Skip to content

Add display option for multi-scroll filter selection by column with threshold indicators#203

Merged
d33bs merged 22 commits into
cytomining:mainfrom
d33bs:multi-scroll-filter
Mar 17, 2026
Merged

Add display option for multi-scroll filter selection by column with threshold indicators#203
d33bs merged 22 commits into
cytomining:mainfrom
d33bs:multi-scroll-filter

Conversation

@d33bs

@d33bs d33bs commented Mar 15, 2026

Copy link
Copy Markdown
Member

Description

This PR adds display options for multi-scroll filter selection by column with threshold indicators. It will line up with work needed in cosmicqc.

Some screenshots:

Single:
image

Multi:
image

Multi with threshold indication:
image

What kind of change(s) are included?

  • Documentation (changes docs or other related content)
  • Bug fix (fixes an issue).
  • Enhancement (adds functionality).
  • Breaking change (these changes would cause existing functionality to not work as expected).

Checklist

Please ensure that all boxes are checked before indicating that this pull request is ready for review.

  • I have read and followed the CONTRIBUTING.md guidelines.
  • I have searched for existing content to ensure this is not a duplicate.
  • I have performed a self-review of these additions (including spelling, grammar, and related).
  • These changes pass all pre-commit checks.
  • I have added comments to my code to help provide understanding
  • I have added a test which covers the code changes found within this PR
  • I have deleted all non-relevant text in this pull request template.

Summary by CodeRabbit

  • New Features

    • Interactive per-column filter sliders with inline distribution visuals and responsive loading feedback for CytoDataFrame tables.
  • Documentation

    • Clarified notebook/table display behavior (respects pandas display settings, midpoint ellipsis, truncation control).
    • Added a DAPI-filtered nuclear speckles example, added an RNA variance column, and removed an older 3D-read example.
  • Tests

    • Extensive tests covering slider/filter behavior, distribution rendering, thresholds, and rendering paths.
  • Chores

    • Bumped pre-commit hook versions and minor project formatting/per-file-ignore tweaks.

@coderabbitai

coderabbitai Bot commented Mar 15, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Implements per-column KDE-style filter sliders and rendering hooks in CytoDataFrame; updates tests, an example and README docs; bumps two pre-commit hooks and adds a per-file flake-ignore in pyproject.toml.

Changes

Cohort / File(s) Summary
Core filter UI
src/cytodataframe/frame.py
Adds per-column filter sliders, KDE-like distribution rendering, slider sizing/constants, threshold/clamping logic, slider state persistence, change handlers, UI loading indicator, and integration into repr/HTML/Trame render flows; updates widget state and exported storage.
Tests
tests/test_frame.py
Adds extensive tests for slider behavior, state updates, loading indicator, range filtering, distribution HTML generation, threshold handling/clamping, rendering paths (notebook vs Trame), caching/rounding, and output clearing.
Documentation & Examples
README.md, docs/src/examples/cytodataframe_at_a_glance.py
README: documents pandas display interaction and truncation behavior. Example: adds DAPI-filtered nuclear-speckles usage, new RNA variance display column, and removes a prior 3D-read block.
Tooling / Config
.pre-commit-config.yaml, pyproject.toml
Bumps two pre-commit hook revisions and adds a per-file E402 ignore for the example; minor formatting tweak in pyproject. Verify CI/lint implications.

Sequence Diagram

sequenceDiagram
    actor User
    participant Widget as Filter Slider Widget
    participant Frame as CytoDataFrame
    participant Renderer as HTML/Trame Renderer
    participant Display as Notebook Display

    User->>Widget: adjust slider range
    Widget->>Frame: _on_filter_slider_change(event)
    Frame->>Frame: update _widget_state.filter_ranges
    Frame->>Frame: compute filtered indices (_filter_display_indices_by_widget_range)
    Frame->>Renderer: request render with filters
    Renderer->>Renderer: build KDE SVG + slider controls
    Renderer->>Display: emit filtered HTML (controls + rows)
    Display->>User: show updated table
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐰 I nibble bytes and prune the rows,
Soft KDE hills where tiny data grows,
I twiddle sliders till the view is small,
Notebooks hum and answer when I call,
Hooray — the table listens to my thrall.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature addition: multi-scroll filter selection by column with threshold indicators, which aligns with the extensive changes to frame.py implementing per-column filter UI and the PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

@d33bs d33bs marked this pull request as ready for review March 16, 2026 03:56
@d33bs d33bs requested a review from jenna-tomkinson as a code owner March 16, 2026 03:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
tests/test_frame.py (2)

977-979: These exact SVG coordinate assertions are brittle.

Line 977–Line 979 hard-code specific y-values (6.00, 22.00), so minor non-functional layout tuning can break tests. Prefer asserting semantic presence (threshold line exists with expected color) plus relative placement tolerances.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_frame.py` around lines 977 - 979, The three brittle assertions
check exact SVG y coordinates in control.children[0].value; instead parse the
SVG element string to assert the threshold line exists with stroke="#dc2626" and
validate its y1/y2 numerically with a tolerance or relative check (e.g., parse
y1 and y2 as floats and assert abs(y1-expected) < tol and abs(y2-expected) < tol
or assert (y2 - y1) matches expected length within tolerance); update the
assertions around control.children[0].value to use color presence plus
tolerant/relative numeric comparisons rather than exact string equality.

933-933: Refactor to use the public CSS class API instead of the private _dom_classes attribute.

The test asserts on _dom_classes, which is an underscore-prefixed trait and not part of ipywidgets' stable public API. This creates potential version-coupling and brittle test failures. Use the public API methods add_class(...) / remove_class(...) to manage CSS classes, then verify the expected state through the widget's public contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_frame.py` at line 933, The test is inspecting the private
attribute slider._dom_classes; change it to use ipywidgets' public class API:
call slider.add_class("cdf-filter-range-slider") where the class is set (or
ensure the code under test uses add_class) and then assert the presence via the
public contract by checking slider.get_state()/widget serialization or another
public accessor rather than _dom_classes — or assert that calling
slider.remove_class("cdf-filter-range-slider") changes the widget state
accordingly; reference the slider variable and replace any direct access to
_dom_classes with add_class/remove_class and assertions that rely on public
behavior (e.g., serialized state or class-manipulation effects).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cytodataframe/frame.py`:
- Around line 657-695: The slider is building one option per distinct numeric
value (unique_values → options) which can be thousands; instead cap the number
of stops before building the widget: introduce a constant (e.g.,
MAX_FILTER_SLIDER_STOPS) and if len(unique_values) > MAX_FILTER_SLIDER_STOPS,
replace options with a reduced set generated from the column domain (use min/max
from numeric_values and generate MAX_FILTER_SLIDER_STOPS evenly spaced tick
values or sample evenly from sorted unique_values), ensure
default_lower/default_upper and normalized_range use the capped domain bounds,
and keep the rest of the SelectionRangeSlider construction
(SelectionRangeSlider, options, value) unchanged so the widget remains
performant for near-unique numeric columns.
- Around line 4257-4287: display_indices is currently computed on the
pre-filtered head/tail window, causing matches in the full frame to be missed;
instead apply the widget filter(s) to the entire DataFrame first and only then
derive the displayed indices. Concretely: detect
active_filter_columns/active_filter_ranges from
self._custom_attrs["_widget_state"], apply those ranges to filter self (or local
data) across all rows (e.g. compute a mask for the tuple ranges and do data =
data.loc[mask] or use existing filtering helper), then call
self._filter_display_indices_by_widget_range(...) or compute display_indices =
data.index.tolist() for the head/tail window on the already-filtered data;
update the branches that currently do data = data.loc[display_indices] so they
operate on full-frame-filtered data instead. Ensure you keep the same symbol
names (display_indices, _filter_display_indices_by_widget_range,
active_filter_columns, active_filter_ranges, data) so the change is localized
and clear.
- Around line 4733-4769: _current implementation of
_try_render_trame_widget_table returns immediately after rendering the trame
widget snapshot which skips building sliders/threshold plots when
display_options contains filter_columns; update the function to detect when
display_options.get("filter_columns") is set (and non-empty) and in that case do
not short-circuit — either avoid the early return after display(...) /
display(HTML(...)) or explicitly call the code path that builds the notebook GUI
(e.g., invoke self._render_notebook_widget_output or the same helper used by it)
so sliders/threshold plots are created for frames that support 3D; touch the
_try_render_trame_widget_table function and the path around show_widget_table /
return True to ensure filters are respected.

In `@tests/test_frame.py`:
- Around line 982-1002: The test name is misleading: the test actually asserts
that an out-of-range threshold is rendered (red stroke) and a warning is logged,
so rename the test function to reflect that behavior (for example,
test_filter_slider_control_warns_and_clamps_out_of_range_threshold) and update
any references; locate the test function by its current name
test_filter_slider_control_ignores_out_of_range_threshold and the helper under
test _build_filter_slider_control_for_column, then update the function name and
its docstring or comment (if present) to match the expectation that the control
shows a clamped/out-of-range indicator and logs a warning.

---

Nitpick comments:
In `@tests/test_frame.py`:
- Around line 977-979: The three brittle assertions check exact SVG y
coordinates in control.children[0].value; instead parse the SVG element string
to assert the threshold line exists with stroke="#dc2626" and validate its y1/y2
numerically with a tolerance or relative check (e.g., parse y1 and y2 as floats
and assert abs(y1-expected) < tol and abs(y2-expected) < tol or assert (y2 - y1)
matches expected length within tolerance); update the assertions around
control.children[0].value to use color presence plus tolerant/relative numeric
comparisons rather than exact string equality.
- Line 933: The test is inspecting the private attribute slider._dom_classes;
change it to use ipywidgets' public class API: call
slider.add_class("cdf-filter-range-slider") where the class is set (or ensure
the code under test uses add_class) and then assert the presence via the public
contract by checking slider.get_state()/widget serialization or another public
accessor rather than _dom_classes — or assert that calling
slider.remove_class("cdf-filter-range-slider") changes the widget state
accordingly; reference the slider variable and replace any direct access to
_dom_classes with add_class/remove_class and assertions that rely on public
behavior (e.g., serialized state or class-manipulation effects).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 565d3134-c1a9-4f56-b579-84ef404a4182

📥 Commits

Reviewing files that changed from the base of the PR and between d61c01e and 9c80a86.

📒 Files selected for processing (6)
  • .pre-commit-config.yaml
  • README.md
  • docs/src/examples/cytodataframe_at_a_glance.ipynb
  • docs/src/examples/cytodataframe_at_a_glance.py
  • src/cytodataframe/frame.py
  • tests/test_frame.py

Comment thread src/cytodataframe/frame.py
Comment thread src/cytodataframe/frame.py Outdated
Comment thread src/cytodataframe/frame.py Outdated
Comment thread tests/test_frame.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/test_frame.py (2)

1159-1166: Consider extracting duplicated display-option patch setup.

The option dictionaries and get_option monkeypatch pattern are repeated. A small helper/fixture would reduce duplication and make future option-shape changes easier to maintain.

Also applies to: 1191-1199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_frame.py` around lines 1159 - 1166, Extract the duplicated
display-options dict and monkeypatch pattern into a single helper or pytest
fixture (e.g., patch_display_options or display_options_fixture) and use that
helper in tests instead of repeating the dict + monkeypatch.setattr calls;
update the locations that currently call the inline options and
monkeypatch.setattr (the blocks around the options variable and
monkeypatch.setattr("cytodataframe.frame.get_option", ...)) to call the new
helper/fixture so future changes to display options are made in one place.

1007-1009: Reduce brittleness from exact SVG coordinate/string assertions.

A few checks depend on exact serialized formatting (e.g., fixed y1/y2 values and strict attribute patterns). Consider asserting semantic properties only (presence of threshold line + in-track position bounds) to keep tests stable across harmless SVG formatting/layout tweaks.

Also applies to: 1089-1096

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_frame.py` around lines 1007 - 1009, Replace brittle exact-string
assertions against control.children[0].value (e.g., "stroke='#dc2626'",
"y1='6.00'", "y2='22.00'") with semantic checks: assert the SVG contains the
threshold line (check for the stroke color attribute value rather than exact
quoting/spacing) and extract the numeric y1/y2 values from the element string,
then assert they lie within expected bounds or within a small tolerance range.
Apply the same change to the similar assertions around lines 1089-1096 so tests
validate presence and approximate position of the threshold line instead of
exact serialized formatting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cytodataframe/frame.py`:
- Around line 4779-4785: The trame bypass guard only inspects
display_options.get("filter_columns") so legacy single-filter config under
"filter_column" is ignored; update the guard around configured_filter_columns
(the block that computes configured_filter_columns and force_trame) to also
check display_options.get("filter_column") and treat a truthy "filter_column"
the same as a configured_filter_columns value (i.e., cause the function to
return False/short-circuit), ensuring both "filter_columns" and legacy
"filter_column" are honored.

---

Nitpick comments:
In `@tests/test_frame.py`:
- Around line 1159-1166: Extract the duplicated display-options dict and
monkeypatch pattern into a single helper or pytest fixture (e.g.,
patch_display_options or display_options_fixture) and use that helper in tests
instead of repeating the dict + monkeypatch.setattr calls; update the locations
that currently call the inline options and monkeypatch.setattr (the blocks
around the options variable and
monkeypatch.setattr("cytodataframe.frame.get_option", ...)) to call the new
helper/fixture so future changes to display options are made in one place.
- Around line 1007-1009: Replace brittle exact-string assertions against
control.children[0].value (e.g., "stroke='#dc2626'", "y1='6.00'", "y2='22.00'")
with semantic checks: assert the SVG contains the threshold line (check for the
stroke color attribute value rather than exact quoting/spacing) and extract the
numeric y1/y2 values from the element string, then assert they lie within
expected bounds or within a small tolerance range. Apply the same change to the
similar assertions around lines 1089-1096 so tests validate presence and
approximate position of the threshold line instead of exact serialized
formatting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e067dba-9a14-4dbb-a392-18b0272fc6cc

📥 Commits

Reviewing files that changed from the base of the PR and between 9c80a86 and b5731b9.

📒 Files selected for processing (5)
  • docs/src/examples/cytodataframe_at_a_glance.ipynb
  • docs/src/examples/cytodataframe_at_a_glance.py
  • pyproject.toml
  • src/cytodataframe/frame.py
  • tests/test_frame.py
✅ Files skipped from review due to trivial changes (1)
  • pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/src/examples/cytodataframe_at_a_glance.py

Comment thread src/cytodataframe/frame.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances CytoDataFrame’s notebook display by adding per-column (and multi-column) interactive range filters, including an overlaid distribution plot with optional threshold indicators, and updates documentation/pre-commit config to support the new display behavior.

Changes:

  • Add interactive filter range sliders (single or multi-column via accordion) with SVG distribution overlays and optional threshold markers.
  • Apply widget-selected filter ranges to notebook HTML rendering (including correct handling relative to pandas display truncation).
  • Update docs/README notes about pandas display truncation + bump pre-commit tool versions.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/cytodataframe/frame.py Implements filter slider state, UI controls, distribution/threshold rendering, and filtering integration into HTML/table rendering.
tests/test_frame.py Adds unit tests for slider state updates, filtering behavior, distribution/threshold rendering, and notebook/trame rendering paths.
docs/src/examples/cytodataframe_at_a_glance.py Adds an example demonstrating display_options["filter_columns"].
README.md Documents notebook/widget row truncation behavior (ellipsis row) aligned with pandas display settings.
pyproject.toml Adds a Ruff per-file ignore for the docs example and minor formatting cleanup.
.pre-commit-config.yaml Bumps pyproject-fmt and ruff-pre-commit revisions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/cytodataframe/frame.py Outdated
Comment thread src/cytodataframe/frame.py Outdated
Comment thread src/cytodataframe/frame.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/cytodataframe/frame.py (1)

4885-4889: Keep traceback context in trame fallback logging.

Line 4885 intentionally falls back, but the current log message drops stack details, which makes render failures hard to debug.

🔧 Small logging improvement
-        except Exception as exc:
-            logger.debug(
-                "Trame widget table render failed, falling back to HTML: %s",
-                exc,
-            )
+        except Exception:
+            logger.debug(
+                "Trame widget table render failed, falling back to HTML.",
+                exc_info=True,
+            )
             return False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cytodataframe/frame.py` around lines 4885 - 4889, The fallback except
block that currently logs "Trame widget table render failed, falling back to
HTML" in frame.py (around the try/except that handles Trame widget table
rendering) drops the traceback; change the logging to include full exception
context by using logger.exception(...) or logger.debug(..., exc_info=True)
instead of the current logger.debug call so the stack trace is preserved when
rendering fails (update the except Exception as exc: handler where the message
is emitted).
tests/test_frame.py (1)

1032-1052: Strengthen clamping assertion to match the test name.

On Line 1032, the test name says “clamps,” but current assertions only verify warning + threshold line presence. Add an x-position check at the domain edge to validate actual clamping behavior.

Proposed assertion tightening
 def test_filter_slider_control_warns_and_clamps_out_of_range_threshold(
     caplog: pytest.LogCaptureFixture,
 ) -> None:
@@
     with caplog.at_level(logging.WARNING):
         _slider, control = cdf._build_filter_slider_control_for_column("FilterScore")
@@
     assert isinstance(control, widgets.VBox)
     assert isinstance(control.children[0], widgets.HTML)
     assert "stroke='#dc2626'" in control.children[0].value
+    html = control.children[0].value
+    x_match = re.search(r"x1='([0-9.]+)' y1='[0-9.]+'", html)
+    assert x_match is not None
+    x_val = float(x_match.group(1))
+    track_right = float(
+        FILTER_SLIDER_TOTAL_WIDTH_PX - FILTER_SLIDER_READOUT_WIDTH_PX
+    )
+    assert abs(x_val - track_right) < 8.0
     assert "outside data range" in caplog.text
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_frame.py` around lines 1032 - 1052, The test currently checks for
a warning and the presence of the threshold SVG stroke but doesn't assert that
the slider indicator was clamped to the data domain; update
test_filter_slider_control_warns_and_clamps_out_of_range_threshold to also
assert the threshold mark's x-position equals the corresponding domain edge from
cdf._custom_attrs["_widget_state"]["filter_ranges"]["FilterScore"] after calling
cdf._build_filter_slider_control_for_column("FilterScore"); locate the SVG/HTML
in control.children[0].value (the same string where you asserted
"stroke='#dc2626'") and add a check that the threshold marker's x attribute (or
equivalent position representation in that HTML/SVG string) matches the clamped
edge value (min or max) derived from the filter_ranges for "FilterScore".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cytodataframe/frame.py`:
- Around line 1205-1208: The current filtering creates `allowed =
set(in_range.index.tolist())` which drops duplicate index labels; instead
preserve duplicate row identities by building a multiset of allowed labels
(e.g., collections.Counter from `in_range.index`) and then iterate
`active_indices` consuming counts so each occurrence is matched once; replace
the set-based membership logic that assigns `allowed` and the list comprehension
over `active_indices` with a loop that checks and decrements the Counter for
`in_range.index` so duplicate labels remain in the filtered `active_indices`.

---

Nitpick comments:
In `@src/cytodataframe/frame.py`:
- Around line 4885-4889: The fallback except block that currently logs "Trame
widget table render failed, falling back to HTML" in frame.py (around the
try/except that handles Trame widget table rendering) drops the traceback;
change the logging to include full exception context by using
logger.exception(...) or logger.debug(..., exc_info=True) instead of the current
logger.debug call so the stack trace is preserved when rendering fails (update
the except Exception as exc: handler where the message is emitted).

In `@tests/test_frame.py`:
- Around line 1032-1052: The test currently checks for a warning and the
presence of the threshold SVG stroke but doesn't assert that the slider
indicator was clamped to the data domain; update
test_filter_slider_control_warns_and_clamps_out_of_range_threshold to also
assert the threshold mark's x-position equals the corresponding domain edge from
cdf._custom_attrs["_widget_state"]["filter_ranges"]["FilterScore"] after calling
cdf._build_filter_slider_control_for_column("FilterScore"); locate the SVG/HTML
in control.children[0].value (the same string where you asserted
"stroke='#dc2626'") and add a check that the threshold marker's x attribute (or
equivalent position representation in that HTML/SVG string) matches the clamped
edge value (min or max) derived from the filter_ranges for "FilterScore".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12bf4c18-4744-4334-8e73-bf40a68ac01e

📥 Commits

Reviewing files that changed from the base of the PR and between f91c1cf and f08ca48.

📒 Files selected for processing (4)
  • .pre-commit-config.yaml
  • docs/src/examples/cytodataframe_at_a_glance.ipynb
  • src/cytodataframe/frame.py
  • tests/test_frame.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .pre-commit-config.yaml

Comment thread src/cytodataframe/frame.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/cytodataframe/frame.py (1)

1275-1278: ⚠️ Potential issue | 🟠 Major

Filter on row positions, not index labels.

set(in_range.index) collapses duplicate labels, and the later full-frame slice re-applies the result through .loc[...]. On a non-unique index, that can resurrect out-of-range rows or duplicate matches in the rendered table. Keep this selection as row positions or a boolean mask until the final slice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cytodataframe/frame.py` around lines 1275 - 1278, The current code
filters active_indices by label membership using set(in_range.index) which
collapses duplicates and later re-applies .loc, causing incorrect rows on
non-unique indexes; change this to filter by row positions or a boolean mask
until the final slice: compute positional indices for the rows in in_range
(e.g., use self._frame.index.get_indexer(in_range.index) or
np.flatnonzero(self._frame.index.isin(in_range.index)) to produce
allowed_positions or a boolean mask) and then filter active_indices against
those positions (not labels), leaving the final .loc slice to use the original
labels only once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cytodataframe/frame.py`:
- Around line 628-633: The code treats an empty list/tuple in configured_many as
"configured" and thus prevents falling back to configured_single (so
{"filter_columns": [], "filter_column":"foo"} yields no filter); modify the
conditional around configured_many so it only takes the first branch when it's a
non-empty sequence (e.g., replace "isinstance(configured_many, (list, tuple))"
with a check that the sequence is non-empty such as "isinstance(configured_many,
(list, tuple)) and len(configured_many) > 0" or simply "configured_many" ),
leaving the subsequent elif branches for configured_many is not None and
configured_single unchanged; reference symbols: configured_many,
configured_single, filter_columns, filter_column.

---

Duplicate comments:
In `@src/cytodataframe/frame.py`:
- Around line 1275-1278: The current code filters active_indices by label
membership using set(in_range.index) which collapses duplicates and later
re-applies .loc, causing incorrect rows on non-unique indexes; change this to
filter by row positions or a boolean mask until the final slice: compute
positional indices for the rows in in_range (e.g., use
self._frame.index.get_indexer(in_range.index) or
np.flatnonzero(self._frame.index.isin(in_range.index)) to produce
allowed_positions or a boolean mask) and then filter active_indices against
those positions (not labels), leaving the final .loc slice to use the original
labels only once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 47513feb-107b-4c89-a334-5a6562a4b9cc

📥 Commits

Reviewing files that changed from the base of the PR and between f08ca48 and 8be4632.

📒 Files selected for processing (1)
  • src/cytodataframe/frame.py

Comment thread src/cytodataframe/frame.py Outdated
@d33bs

d33bs commented Mar 17, 2026

Copy link
Copy Markdown
Member Author

@jenna-tomkinson - I think this is ready for a review. I tried to use some data from cosmicqc and the filter columns here and this is how it currently ends up looking with the NF1 test dataset:
image

@jenna-tomkinson jenna-tomkinson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM! I did leave some feedback regarding how the KDE plots are generated that will likely lead to issues with visualization, like when distributions are skewed they will look flat. This work is a great start and lift!

Comment thread src/cytodataframe/frame.py
Comment thread src/cytodataframe/frame.py
Comment thread src/cytodataframe/frame.py
@d33bs

d33bs commented Mar 17, 2026

Copy link
Copy Markdown
Member Author

Thanks @jenna-tomkinson ! Things are looking a lot better now after making changes based on your comments:
image

I'll merge this in after committing the changes.

Co-Authored-By: Jenna Tomkinson <107513215+jenna-tomkinson@users.noreply.github.com>
@d33bs d33bs merged commit cf60c30 into cytomining:main Mar 17, 2026
9 checks passed
@d33bs d33bs deleted the multi-scroll-filter branch March 17, 2026 19:42
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.

3 participants