feat(psychometric): expose grand-mean-centered event-time lag - #332
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Local verify at
Hamaker, Kuiper, and Grasman (2015, p. 104) is the claim boundary: CGM lagged relations confound stable between-person differences with within-person change. T=2 CWC is empty ( Do not self-approve. Independent non-author APPROVE is still missing. naru-osg invitation 331032327 remains pending (read, 2026-08-30T20:53:43Z). Only collaborator is the author, so review cannot be requested from a collaborator. |
| let mut groups: BTreeMap<u64, Vec<ClusteredEventScore>> = BTreeMap::new(); | ||
| let mut score_sum = 0.0_f64; | ||
| for &row in rows { | ||
| if !row.event_time.is_finite() || !row.score.is_finite() { | ||
| return Err(PsychometricError::InvalidNumericInput); | ||
| } | ||
| score_sum += row.score; | ||
| groups.entry(row.cluster_key).or_default().push(row); | ||
| } | ||
| if groups.len() < 2 { | ||
| return Err(PsychometricError::InsufficientClusters); | ||
| } | ||
| let grand_mean = score_sum / (rows.len() as f64); | ||
| if !grand_mean.is_finite() { | ||
| return Err(PsychometricError::InvalidNumericInput); | ||
| } | ||
| let mut pairs = Vec::new(); | ||
| for occasions in groups.values_mut() { | ||
| if occasions.len() < 2 { | ||
| continue; |
There was a problem hiding this comment.
| for pair in lagged { | ||
| if !same_sign_nonzero(pair.earlier_residual, pair.later_residual) { | ||
| continue; | ||
| } | ||
| sum += voelkle_same_sign_log_rate( | ||
| pair.earlier_residual, | ||
| pair.later_residual, | ||
| pair.event_delta, | ||
| )?; |
There was a problem hiding this comment.
|
#327 head is now |
dbb8108 to
4db3d9b
Compare
|
Rebased onto #327 Kept the Hamaker et al. (2015, p. 104) CGM estimand. Wired Local after rebase:
Unpaywall 2026-08-30T21:36Z: Meredith (1993) and Mislevy (1991) remain |
seonghobae
left a comment
There was a problem hiding this comment.
Author COMMENT on 4db3d9b, not approval. Rebased onto #327 79262df. Independent non-author APPROVE is still required. Do not self-approve.
| /// Grand-mean-center consecutive event-time lags (not a within-person lag). | ||
| pub use event_time::center_grand_mean_event_lags; |
There was a problem hiding this comment.
Follow-up, not a PRD version bump. center_grand_mean_event_lags is a refuse-closed sibling estimand: Hamaker, Kuiper, and Grasman (2015, p. 104) show lagged relations from grand-mean deviations are not within-person change. refuse_grand_mean_centered_log_rate_as_within_person_lag always fails closed. ADR 0005 already records the distinction. The approved product/measurement target is unchanged, so AGENTS.md item 14 does not require a PRD version change for this slice.
4db3d9b to
147c844
Compare
|
Author note on Nightly unique-site branch coverage on predecessor
Also split the merged grand-mean and already-centered research bullets. Local verify on Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success. |
seonghobae
left a comment
There was a problem hiding this comment.
Author COMMENT on 147c844, not approval. Rebased onto #327 cab5e58. Independent non-author APPROVE still required. Do not self-approve.
| pairs.push(LaggedWithinResidual { | ||
| earlier_residual, | ||
| later_residual, | ||
| event_delta, | ||
| }); |
There was a problem hiding this comment.
🟡 Grand-mean deviations bypass within-person refusal
center_grand_mean_event_lags returns LaggedWithinResidual, which recover_irregular_centered_residual_log_rate accepts as within-person input. Their direct composition succeeds, bypassing the mandatory refusal.
Prompt for agents
The new center_grand_mean_event_lags function constructs LaggedWithinResidual values, but that public type represents already-centered within-person residuals and is accepted by recover_irregular_centered_residual_log_rate. The PR's own tests demonstrate that composing these APIs succeeds. Introduce a distinct type or provenance-aware API for grand-mean deviations so they cannot enter the within-person recovery path. Keep the grand-mean-specific recovery available, and make any attempted reinterpretation as a within-person lag fail closed as required by CLAUDE.md.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Addressed on d703946. center_grand_mean_event_lags now returns LaggedGrandMeanResidual. That type cannot enter recover_irregular_centered_residual_log_rate (Hamaker, Kuiper, & Grasman, 2015, p. 104).
| score_sum += row.score; | ||
| groups.entry(row.cluster_key).or_default().push(row); |
There was a problem hiding this comment.
🟡 Row ordering changes valid centering
When finite scores cancel mathematically, score_sum += row.score can overflow for one row order but not another. Identical observations then alternate between valid pairs and InvalidNumericInput.
Prompt for agents
center_grand_mean_event_lags computes score_sum with naive sequential addition before grouping and sorting. A dataset containing +f64::MAX and -f64::MAX can succeed in an alternating order but overflow in an order that places the positive values first, despite having the same finite mathematical grand mean and valid residuals. Compute the grand mean with an overflow-resistant, permutation-stable f64 strategy, and add a permutation test using identical rows in both orders.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Addressed on d703946. Finite raw sums still use sum/n so coinciding cluster means match CWC. When that sum overflows, positives and negatives are averaged separately with the overflow-safe incremental mean and combined by count, so cancelling ±MAX scores keep a representable zero grand mean in either row order.
|
Author COMMENT on Devin on
Local verify on
Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success. naru-osg invitation 331032327 remains pending (read). Only collaborator is the author, so review cannot be requested from a collaborator. |
Public CGM pipeline is a distinct estimand from CWC. Hamaker, Kuiper, and Grasman (2015, p. 104) show that lagged relations from grand-mean deviations confound stable between-person differences with within-person change. T=2 CWC is always r, -r; T=2 CGM can keep same-sign pairs. Not CWC, not RI-CLPM, not DSEM.
…h items Nightly branch coverage on 4db3d9b was 3949/3960. center_grand_mean_event_lags never took !event_time.is_finite(); overflow/underflow reconstructions used dead && and continue arms. Split the merged grand-mean and already-centered research bullets. Hamaker, Kuiper, and Grasman (2015) still names this not a within-person lag. Not DSEM. Meredith (1993) / Mislevy (1991) still unread.
Devin on 147c844: LaggedWithinResidual from CGM bypassed recover_irregular_centered_residual_log_rate. Naive score_sum of ±MAX overflowed in one row order. Hamaker, Kuiper, and Grasman (2015, p. 104): grand-mean lagged relations are not within-person. LaggedGrandMeanResidual cannot enter the within-person helper. Finite raw sums still use sum/n so coinciding cluster means match CWC. Overflowed sums sign-split the overflow-safe incremental mean. Not CWC, not RI-CLPM, not DSEM. Do not self-approve.
d703946 to
e90f3aa
Compare
|
Author COMMENT on Scientific content is unchanged from Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success. |
Public center_grand_mean_event_lags rustdoc linked crate-private overflow_safe_running_mean. cargo doc -Dwarnings failed the warning-free documentation job on e90f3aa. Name the overflow-safe incremental mean in prose instead. Not CWC, not RI-CLPM, not DSEM. Do not self-approve.
seonghobae
left a comment
There was a problem hiding this comment.
Author COMMENT on dabb769, not approval.
cargo doc -Dwarnings failed on predecessor e90f3aa because public center_grand_mean_event_lags rustdoc linked crate-private overflow_safe_running_mean. The public docs now name the overflow-safe incremental mean in prose (Hamaker, Kuiper, & Grasman, 2015, p. 104). Local: rustdoc -Dwarnings clean; clippy -D warnings clean; lib 215, scientific-claim 53, multilevel 109.
Independent non-author APPROVE is still required. Do not self-approve. Do not merge without that APPROVE and exact-head required-check success.
|
Author COMMENT on Warning-free rustdoc failed on Independent non-author APPROVE is still required. Do not self-approve. |
Form p_it = x_it − μ_t on aligned waves and map consecutive residuals through Voelkle et al. (2012, Eq. 7). Those residuals still contain trait-like between-person deviations, so the recovered rate is refused as a within-person lag. This is not RI-CLPM, not CWC, not the #332 sample grand mean, and not DSEM.
Stacked on #327 (
cab5e58). One atomic public grand-mean-centered event-time lag on that CWC head. Not CWC. Not RI-CLPM. Not DSEM. Not a second invariance crate. Does not add another Driver p.16 std restoration.Maps (distinct from #327 CWC).
center_grand_mean_event_lagssubtracts the sample grand mean, then emits consecutive residuals asLaggedWithinResidualpairs. Consecutive times are ordered withf64::total_cmp. Singleton clusters are skipped.recover_grand_mean_centered_irregular_residual_log_rateis the pairwise mean of Voelkle, Oud, Davidov, and Schmidt (2012, Eq. 7) on nonzero same-sign residuals. When|later| / |earlier|is finite the rate isln(|later| / |earlier|) / Δt; overflowed or underflowed ratios use(ln|later| − ln|earlier|) / Δt. The pairwise mean is incremental so two finite rates whose raw sum overflows stay representable.refuse_grand_mean_centered_log_rate_as_within_person_lagalways fails closed.Why this is not a within-person lag. Hamaker, Kuiper, and Grasman (2015, p. 104; UvA PDF opened 2026-08-30T20:58Z from https://pure.uva.nl/ws/files/2688454/168970_Hamaker_Kuiper_Grasman_2015_A_Critique_of_Cross_Lagged_Panel_Model.pdf) show that lagged relations formed from deviations around shared (grand) means implicitly assume there are no trait-like between-person differences. Those differences contaminate coefficients supposed to represent within-person change. The RI-CLPM lagged relations use within-person deviations from stable means rather than grand means. This helper is not RI-CLPM.
T=2 distinction from CWC. T=2 CWC is always
r, −r(empty admissible,InvalidNumericInput). T=2 CGM can keep same-sign pairs when clusters sit on opposite sides of the grand mean. When cluster means coincide, CGM equals CWC.Fail closed. Non-event clock, empty/singleton-only series, fewer than two clusters, non-positive interval, overflowing grand mean, finite grand mean whose residual overflows, tiny
Δtwith a huge log-ratio, a non-finite event time, and empty admissible set after skipping zero/opposite-sign pairs. Treating the CGM log-rate as a within-person lag always fails closed.Local verify (
147c844).cargo fmton touched Rust;clippy -p psychometric_core --all-targets --all-features -- -D warningsclean;cargo test -p psychometric_core: lib 212, scientific-claim 53, multilevel 109.Do not self-approve. Independent non-author APPROVE is required before squash-merge, together with exact-head required-check success. Queued/pending/skipped/cancelled/stale/predecessor-head is non-passing.