Skip to content

Feature/var detected by cat upset - #40

Open
leventetn wants to merge 6 commits into
mainfrom
feature/var_detected_by_cat_upset
Open

leventetn wants to merge 6 commits into
mainfrom
feature/var_detected_by_cat_upset

Conversation

@leventetn

Copy link
Copy Markdown
Collaborator

Tests: add spec-guided TDD support conftest

Add tests/conftest.py from the spec-guided-tdd skill, formatted to
ProteoPy's black/flake8 standards. It only affects test modules
marked `pytestmark = pytest.mark.spec_guided` (e.g.
tests/pl/test_upset.py); all other tests run unchanged.

- `pytest` (default, CI) runs the holdout and randomized tests and
  deselects the `_IMP` mirrors
- `pytest --implementer` runs the `_IMP` and randomized tests with
  obscured failure output, for implementation sessions
- `--seed N` reproduces randomized inputs (seed printed in the header)
- provides the `rng` and `report_input` fixtures
- checks test naming and holdout/_IMP pairing
Tests: pl.var_detected_by_cat_upset()

Add a spec-guided test suite for var_detected_by_cat_upset()
(module marked `spec_guided`, run by tests/conftest.py):

- TestVarDetectedByCatUpset: 75 holdout tests plus 9 randomized
  property and metamorphic tests
- TestVarDetectedByCatUpsetIMP: 75 _IMP mirrors on different inputs,
  run only with `pytest --implementer`

Coverage: intersection counts and thresholds (min_count /
min_fraction, inclusivity, per-category denominators, empty
categories, float edge cases), zero/NaN detection, category order
and str coercion, sparse input, non-mutation (incl. views), UpSet
construction and rendered axes, print_stats tables (incl. column-name
collisions), verbose output, save/show, and argument validation.

UpSet calls are observed through spies on the upsetplot class;
randomized tests use the seeded rng fixture (reproduce with
`--seed N`).
Feature: pl.var_detected_by_cat_upset()

Add an UpSet plot of which features (.var) are detected in which
categories of an .obs column, e.g. which proteins are found in which
tissues.

- A feature is a member of a category when it is detected (non-NaN in
  .X; with zero_to_na=True also non-zero) in enough of that
  category's observations: min_count (default 1) or min_fraction,
  inclusive, per category. Setting both raises ValueError.
- Features that are members of no category form a "No category" set,
  always included, also with a count of 0.
- Categories follow the default pl order: category order for a
  Categorical column (unused categories kept), else lexicographic
  order of the str-coerced values. Values colliding after str
  coercion raise ValueError.
- print_stats prints global, per-intersection and per-category
  tables; verbose reports the input matrix, threshold and sizes.
- Sparse .X is densified with a UserWarning; adata is never modified.
- Returns the axes dict of upsetplot's UpSet.plot().

Add upsetplot>=0.9.0,<0.10 as a core dependency. upsetplot 0.9.0 is
broken on pandas 3 / numpy >= 2.4, so proteopy.pl.upset patches it on
import (dot style defaults, single-category aggregation, count-label
positions, all-empty totals warning). The upper bound keeps these
patches from hitting an untested upsetplot release.

Export via pr.pl, list it under Quality Control in the plotting API
docs, and add a HISTORY.md entry.
@read-the-docs-community

read-the-docs-community Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 proteopy | 🛠️ Build #35071191 | 📁 Comparing a4164b5 against latest (3661567)

  🔍 Preview build  

1 file changed
± changelog.html

@leventetn

Copy link
Copy Markdown
Collaborator Author
image image image

@leventetn
leventetn force-pushed the feature/var_detected_by_cat_upset branch from bee29ba to 2c85530 Compare October 8, 2026 21:39
Implemented sort_by with "degree", "cardinality", "-degree" and
"-cardinality". New tests cover all modes, equal counts and degrees,
repeatability, bar/dot alignment, and invalid inputs.
@leventetn
leventetn force-pushed the feature/var_detected_by_cat_upset branch from 2c85530 to a4164b5 Compare October 11, 2026 09:32

@idf-io idf-io left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR review part 1. "Functionality"

Comment thread proteopy/pl/upset.py
verbose: bool = False,
show: bool = True,
save: str | Path | None = None,
*,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put * after cat_key, only adata and cat_key should be positional arguments.

Comment thread proteopy/pl/upset.py
Comment on lines +579 to +580
ProteoPy AnnData (peptide- or protein-level). Intensities are
read from ``adata.X`` only.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reword: AnnData in ProteoData format.

Comment thread proteopy/pl/upset.py
show : bool
Call ``plt.show()`` at the end.
save : str | Path | None
Path to save the figure to; None skips saving.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"; None skips saving" is redundant.

Comment thread proteopy/pl/upset.py
Call ``plt.show()`` at the end.
save : str | Path | None
Path to save the figure to; None skips saving.
sort_by : str

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename to "intersection_order".
order_by is reserved for a plot when you order the samples/vars by a category and color by them. e.g.

Image

Comment thread proteopy/pl/upset.py

Returns
-------
dict[str, Axes]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If pyupsetplot coopts the matplotlib figure, return None

Comment thread proteopy/pl/upset.py
order after conversion to strings. Use an ordered
:class:`pandas.Categorical` to customize the order.
min_count : int | None
Minimum detections per category, as a nonnegative integer.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it a tiny bit clearer by wording like:
Minimum number of non-missing var values (detections) within a category for the membership to be counted.
Make same improvement to min_fraction

Comment thread proteopy/pl/upset.py
)

# -- Plot
axes = _plot_upset(counts, sort_by)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove axes return. Return None.

Comment thread proteopy/pl/upset.py
Comment on lines +526 to +540
def _print_verbose_report(
cat_key: str,
threshold_name: str,
threshold_value: int | float,
n_vars: int,
n_categories: int,
) -> None:
"""Print a short report about the input of the plot."""
print(
f"Using the .X matrix.\n"
f"Categories from .obs['{cat_key}'].\n"
f"Threshold: {threshold_name} = {str(threshold_value)}\n"
f"Features: {n_vars}\n"
f"Categories: {n_categories}"
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too obvious verbose statements. They don't bring any new information. i dont think this plot needs any verbose output.
Guideline for plotting functions:

  • verbose: non-obvious useful printouts e.g. if several heavy steps are involved, announce them. If expensive looping algorithm, print at which loop we are etc.
  • print_stats: print what we see in the plot so that the user can have tangible numbers to report in a paper. This includes sometimes making stats of what you see: mean of data points...

Comment thread proteopy/pl/upset.py
counts = _intersection_counts(membership, names)

# -- Report
if verbose:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove verbose parameter, see other comment why,

Comment thread proteopy/pl/upset.py
axes = _plot_upset(counts, sort_by)

if save is not None:
axes["matrix"].figure.savefig(save)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually work? From the code I would think that if there are several axes and you only save one of them, we are not saving the entire upset figure. Please check in practice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants