Skip to content

Filter non-positive RTs before KDE bandwidth estimation - #349

Open
EItanm1999 wants to merge 1 commit into
lnccbrown:mainfrom
EItanm1999:fix/kde-nonpositive-rt-filter
Open

Filter non-positive RTs before KDE bandwidth estimation#349
EItanm1999 wants to merge 1 commit into
lnccbrown:mainfrom
EItanm1999:fix/kde-nonpositive-rt-filter

Conversation

@EItanm1999

@EItanm1999 EItanm1999 commented Aug 31, 2026

Copy link
Copy Markdown

A genuinely negative or zero RT - real and expected for models whose non-decision-time kernel has unbounded support (e.g. a Normal(t, st) kernel), and distinct from the omission sentinel - produces log(non-positive) = NaN in the log-RT array. A single NaN poisons np.std() over the entire choice group inside bandwidth_silverman, which then fails the 'bandwidth > 0' check and silently discards the whole group as 'no_base_data'. Measured directly: 1 negative RT out of 198,865 samples collapsed its entire choice group, flattening that group's density to a theta-independent floor in the training labels.

Extend the validity mask to require rt > 0 and finite log(rt), applied to the final (possibly displace_t-shifted) RTs. Clean data is unaffected

  • the regression test pins both behaviors.

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 83171c83-cd51-4c73-a094-0385cb4f3471

📥 Commits

Reviewing files that changed from the base of the PR and between 272d04e and e89f64e.

📒 Files selected for processing (2)
  • ssms/support_utils/kde_class.py
  • tests/support_utils/test_kde_nonpositive_rts.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

LogKDE now removes sentinel, non-positive, and non-finite log reaction times before bandwidth calculation. Regression tests cover invalid samples and confirm that clean input remains unchanged.

Changes

KDE filtering

Layer / File(s) Summary
Filter invalid reaction times and validate KDE data
ssms/support_utils/kde_class.py, tests/support_utils/test_kde_nonpositive_rts.py
_attach_data_from_simulator applies a combined validity mask before storing reaction times. Tests verify that negative values are removed without collapsing choice groups and that clean data retains all samples.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to e89f6

The change filters non-positive and non-finite reaction times before KDE bandwidth estimation, preventing one invalid sample from discarding an entire choice group's density while preserving clean data behavior. No actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: filtering non-positive reaction times before KDE bandwidth estimation.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@EItanm1999

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

A genuinely negative or zero RT - real and expected for models whose
non-decision-time kernel has unbounded support (e.g. a Normal(t, st)
kernel), and distinct from the omission sentinel - produces
log(non-positive) = NaN in the log-RT array. A single NaN poisons
np.std() over the entire choice group inside bandwidth_silverman, which
then fails the 'bandwidth > 0' check and silently discards the whole
group as 'no_base_data'. Measured directly: 1 negative RT out of 198,865
samples collapsed its entire choice group, flattening that group's
density to a theta-independent floor in the training labels.

Extend the validity mask to require rt > 0 and finite log(rt), applied
to the final (possibly displace_t-shifted) RTs.

- The mask stays after the log rather than at it: with displace_t,
  measured 10 of 200 strictly-positive input RTs become non-positive
  only after the shift, so an earlier-only mask misses them. The
  RuntimeWarning from the earlier log is left unsuppressed - it reports
  real data.
- All three mask terms are load-bearing, measured over 2M draws at the
  displace_t boundary: 3 cases where rt - t > 0 but the log path
  underflows to -inf (caught only by isfinite) and 1 in the reverse
  direction (caught only by rt > 0); rt = +inf is caught only by
  isfinite. != filter_rts is kept because filter_rts is a documented
  parameter and rt > 0 subsumes it only for negative sentinels.
- No input existed for which the old code produced a finite bandwidth
  that this changes: contamination always forced no_base_data. Verified
  byte-identical (float hex) bandwidths and log_rts for clean data
  across ddm, ddm_st, full_ddm and ddm_sdv, at n=200 and n=5000.
- Filtering can now leave a choice group with a single sample, where
  bandwidth_silverman returns its std_n_1 fallback of ~10.59 instead of
  collapsing. That is an improvement on a flat fallback but not a
  meaningful bandwidth, so it is documented on the class and pinned by a
  test. Raising the len(log_rts) == 0 floor to < 2 would also change
  pre-existing n=1 behavior and is left as a separate change.
- Retention contract, the pre-filter basis of choice_proportions, and
  the n=1 limitation are now in the LogKDE class docstring, which is
  what the API docs render; the stale caller-precondition sentence in
  the kde_class tutorial is corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@EItanm1999
EItanm1999 force-pushed the fix/kde-nonpositive-rt-filter branch from e89f64e to 696053f Compare September 2, 2026 20:58
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@EItanm1999

Copy link
Copy Markdown
Author

@AlexanderFengler

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.

1 participant