Skip to content

CODAP-1471: omit LSRL statistics that aren't estimable - #2675

Merged
kswenson merged 2 commits into
mainfrom
CODAP-1471-lsrl-undefined-stats
Jul 28, 2026
Merged

CODAP-1471: omit LSRL statistics that aren't estimable#2675
kswenson merged 2 commits into
mainfrom
CODAP-1471-lsrl-undefined-stats

Conversation

@kswenson

Copy link
Copy Markdown
Member

Fixes CODAP-1471

⚠️ Stacked on #2674 (CODAP-1461). Targets that branch, not main — retarget
once #2674 merges. Review only the latest commit; the rest is #2674's diff.

Problem

Three cases where a statistic that can't be estimated still reached the LSRL's output:

Data Before After
Horizontally aligned + Show r/r² y = 5, r = (not a number), r² = 0 y = 5
Two points + Confidence Bands σslope = 0, σintercept = 0, NaN band paths, 3 console errors equation only, paths cleared, no errors
Normal data r, r², σ, bands unchanged

Root causes

  • rSquared is (sumOfProductDiffs)² / (xSumSquaredDeviations * ySumSquaredDeviations),
    which is 0/0 when the y values don't vary. The r branch formatted that NaN directly
    while the r² branch passed it through rSquared || 0 — hence the two disagreeing.
  • linRegrStdErrSlopeAndIntercept special-cased count === 2 to return {0, 0}. With
    zero degrees of freedom the standard errors aren't estimable, so 0 asserts something
    we can't know.
  • mse = sumSquaredErrors / (count - 2) is 0/0 with two points, and confidenceValues
    guarded with == null, which NaN passes. NaN reached the band path d attribute, so
    the browser rejected the paths and logged <path> attribute d: Expected number.

Fix

Per the rule agreed with Bill: anything undefined is omitted — geometry that can't be
drawn isn't drawn, numbers that can't be computed get no row.

  • lsrlEquationString omits the r, r², σslope and σintercept rows unless the value is
    finite, and no longer launders NaN through || 0.
  • linRegrStdErrSlopeAndIntercept drops the count === 2 special case.
  • confidenceValues guards on finiteness rather than nullness, and the component clears
    the band paths instead of assigning empty/NaN ones.

V2 compatibility

I ran v2's own math in the live v2 app (build 0745). v2 produces the same NaNs for every
one of these cases and has no count === 2 → 0 special case — it returns NaN for
seSlope/seIntercept with two points. So removing that special case restores v2 behavior
rather than diverging from it. On 5-point data v3 now yields σslope = 0.15 and
σintercept = 0.497, matching v2's 0.15000000000000005 / 0.49749371855331026.

Testing

  • New lsrlEquationString tests: r/r² omitted when rSquared is NaN, σ rows omitted when
    the standard errors are NaN, plus a positive control that finite values still render.
  • New confidenceValues / confidenceBandsPoints tests: no bounds and no band points
    with two points; finite bounds with four.
  • Updated the two-point linRegrStdErrSlopeAndIntercept expectation from 0 to NaN.
  • Verified in the browser for all three rows of the table above, including that the
    console errors are gone.

🤖 Generated with Claude Code

Three related cases let undefined statistics reach the LSRL's output:

- rSquared is 0/0 when the y values don't vary, so a valid horizontal line
  displayed "r = (not a number)" next to "r² = 0". The r² branch laundered the
  NaN through `rSquared || 0`, which is why the two disagreed.
- The standard errors were reported as 0 with only two points, though the fit
  has zero degrees of freedom and they aren't estimable. V2 returns NaN here,
  so the special case was also a V2 divergence.
- mse is likewise 0/0 with two points, and confidenceValues guarded with
  `== null`, which NaN passes. NaN reached the confidence band path
  coordinates and the browser rejected the paths, logging console errors.

Statistics that aren't estimable are now omitted from the equation rather than
displayed, and bands that can't be estimated clear their paths instead of
drawing NaN coordinates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kswenson kswenson added the v3 CODAP v3 label Jul 27, 2026
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.69%. Comparing base (739dee8) to head (b06718f).

Additional details and impacted files
@@                         Coverage Diff                          @@
##           CODAP-1461-lsrl-degenerate-equation    #2675   +/-   ##
====================================================================
  Coverage                                87.69%   87.69%           
====================================================================
  Files                                      810      810           
  Lines                                    46938    46939    +1     
  Branches                                 12013    12004    -9     
====================================================================
+ Hits                                     41162    41164    +2     
+ Misses                                    5760     5759    -1     
  Partials                                    16       16           
Flag Coverage Δ
cypress 69.50% <100.00%> (+<0.01%) ⬆️
jest 62.71% <70.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cypress

cypress Bot commented Jul 27, 2026

Copy link
Copy Markdown

codap-v3    Run #12102

Run Properties:  status check passed Passed #12102  •  git commit e2bd79a758: null
Project codap-v3
Branch Review main
Run status status check passed Passed #12102
Run duration 07m 54s
Commit git commit e2bd79a758: null
Committer null
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 1
Tests that did not run due to a developer annotating a test with .skip  Pending 82
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 382
View all changes introduced in this branch ↗︎

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 updates the v3 graph LSRL (least-squares regression line) display logic so that regression-related statistics and confidence-band geometry that are not mathematically estimable (e.g., NaN from degenerate inputs like two points or no variance) are omitted/cleared rather than rendered, preventing inconsistent UI output and browser console errors.

Changes:

  • Leave LSRL standard errors as NaN when degrees of freedom are zero (two-point fit), and update unit tests accordingly.
  • Update lsrlEquationString() to omit r, r², and σ rows unless the computed values are finite (and stop laundering NaN through || 0).
  • Guard confidence-band computation/rendering against non-finite inputs and clear SVG paths when bands can’t be estimated.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
v3/src/utilities/stats-utils.ts Stops reporting 0 standard errors for 2-point fits; leaves them non-estimable (NaN).
v3/src/utilities/stats-utils.test.ts Updates expectations to match non-estimable standard errors for 2-point data.
v3/src/components/graph/utilities/graph-utils.ts Omits non-finite LSRL statistics from the equation string (r, r², σ terms).
v3/src/components/graph/utilities/graph-utils.test.ts Adds tests asserting omitted/retained rows based on finiteness of stats.
v3/src/components/graph/adornments/lsrl/lsrl-adornment-model.ts Switches confidence-band bounds gating from nullness to finiteness to avoid NaN propagation.
v3/src/components/graph/adornments/lsrl/lsrl-adornment-model.test.ts Adds tests covering confidence bounds/band points behavior with sufficient vs insufficient degrees of freedom.
v3/src/components/graph/adornments/lsrl/lsrl-adornment-component.tsx Clears band paths when unestimable (returns null paths; avoids empty/NaN path strings).

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

Comment thread v3/src/components/graph/adornments/lsrl/lsrl-adornment-model.ts
The guard reads `line?.intercept` before the unchained `line.slope`, which is
safe because the leading `||` operand short-circuits. Add tests covering the
no-line and unknown-category cases so the behavior is pinned rather than
inferred from reading.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kswenson
kswenson marked this pull request as ready for review July 27, 2026 04:24
@kswenson
kswenson requested a review from bfinzer July 27, 2026 04:25

@bfinzer bfinzer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍🏻LGTM

Base automatically changed from CODAP-1461-lsrl-degenerate-equation to main July 28, 2026 20:02
@kswenson
kswenson merged commit e2bd79a into main Jul 28, 2026
31 checks passed
@kswenson
kswenson deleted the CODAP-1471-lsrl-undefined-stats branch July 28, 2026 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants