Repository navigation
fix(longitudinal): require both marginals for lagged correlation #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
seonghobae
wants to merge
10
commits into
main
from
agent/psychometric-trait-plus-state-expected-autocorrelation
Closed
Changes from 2 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
beba8ea
feat(psychometric): recover Driver §7.1 trait-plus-state expected aut…
seonghobae c1aeed3
fix(longitudinal): retire invalid one-sided autocorrelation claim
seonghobae c8d9176
test(longitudinal): expose exact covariance-bound rounding
seonghobae eef06e7
fix(longitudinal): validate covariance bound exactly
seonghobae 9597893
docs(research): trace lagged correlation claims
seonghobae 21e3696
test(longitudinal): expose unequal-scale correlation underflow
seonghobae f49309a
fix(longitudinal): preserve unequal-scale correlations
seonghobae 8f2b355
chore: stage one-shot longitudinal capability-record repair
seonghobae 6f48322
docs(longitudinal): record lagged-correlation capability
seonghobae 23959d1
fix(longitudinal): cover lagged-correlation validation branches
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| //! Event-time lagged association standardization. | ||
| //! | ||
| //! This module belongs to the Longitudinal Modeling bounded context. It | ||
| //! standardizes a lagged covariance only when both marginal variances are | ||
| //! available. A one-sided covariance/initial-variance ratio is deliberately | ||
| //! not exposed as an autocorrelation. | ||
|
|
||
| use crate::LongitudinalError; | ||
|
|
||
| /// Recover a Pearson correlation for an event-time lag from its covariance and | ||
| /// both marginal variances. | ||
| /// | ||
| /// For observations at event times `t` and `t + Δ`, the correlation is | ||
| /// | ||
| /// `Cov(Y_t, Y_{t+Δ}) / sqrt(Var(Y_t) * Var(Y_{t+Δ}))`. | ||
| /// | ||
| /// Requiring both marginals is essential for nonstationary processes. Driver, | ||
| /// Oud, and Voelkle (2017) provide the continuous-time state-transition and | ||
| /// covariance components from which occasion-specific marginals can be built; | ||
| /// they do not justify replacing the second marginal variance with the first | ||
| /// when the process is nonstationary. | ||
| /// | ||
| /// `event_interval` is semantically required to be strictly positive so a | ||
| /// measurement-occasion or method facet cannot be passed as an untyped lag. | ||
| /// This function does not infer either marginal variance and does not estimate | ||
| /// a state process. | ||
| /// | ||
| /// # Errors | ||
| /// | ||
| /// Returns [`LongitudinalError::InvalidTemporalAssociationInput`] for | ||
| /// non-finite inputs, [`LongitudinalError::NonPositiveMarginalVariance`] when | ||
| /// either marginal variance is not strictly positive, | ||
| /// [`LongitudinalError::NonPositiveEventInterval`] for a non-positive event | ||
| /// interval, and [`LongitudinalError::CovarianceBoundViolation`] when the | ||
| /// supplied covariance is incompatible with the two marginal variances. | ||
| pub fn recover_event_time_lagged_correlation( | ||
| lagged_covariance: f64, | ||
| earlier_total_variance: f64, | ||
| later_total_variance: f64, | ||
| event_interval: f64, | ||
| ) -> Result<f64, LongitudinalError> { | ||
| if !lagged_covariance.is_finite() | ||
| || !earlier_total_variance.is_finite() | ||
| || !later_total_variance.is_finite() | ||
| || !event_interval.is_finite() | ||
| { | ||
| return Err(LongitudinalError::InvalidTemporalAssociationInput); | ||
| } | ||
| if earlier_total_variance <= 0.0 || later_total_variance <= 0.0 { | ||
| return Err(LongitudinalError::NonPositiveMarginalVariance); | ||
| } | ||
| if event_interval <= 0.0 { | ||
| return Err(LongitudinalError::NonPositiveEventInterval); | ||
| } | ||
|
|
||
| // sqrt(v1) * sqrt(v2) avoids the avoidable overflow of sqrt(v1 * v2). | ||
| let denominator = earlier_total_variance.sqrt() * later_total_variance.sqrt(); | ||
| if !denominator.is_finite() || denominator <= 0.0 { | ||
| return Err(LongitudinalError::InvalidTemporalAssociationInput); | ||
| } | ||
| let correlation = lagged_covariance / denominator; | ||
| if !correlation.is_finite() { | ||
| return Err(LongitudinalError::InvalidTemporalAssociationInput); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
| if correlation.abs() > 1.0 { | ||
| return Err(LongitudinalError::CovarianceBoundViolation); | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
| Ok(correlation) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::recover_event_time_lagged_correlation; | ||
| use crate::LongitudinalError; | ||
|
|
||
| #[test] | ||
| fn nonstationary_marginals_do_not_use_the_earlier_variance_twice() { | ||
| // The retired one-sided ratio would be 1.5 and therefore impossible as | ||
| // an autocorrelation. Supplying the later marginal gives 0.75. | ||
| let recovered = recover_event_time_lagged_correlation(1.5, 1.0, 4.0, 1.0) | ||
| .expect("valid nonstationary covariance should standardize"); | ||
| assert!((recovered - 0.75).abs() < f64::EPSILON * 4.0); | ||
| } | ||
|
|
||
| #[test] | ||
| fn covariance_bound_is_fail_closed() { | ||
| assert_eq!( | ||
| recover_event_time_lagged_correlation(2.000_000_000_1, 1.0, 4.0, 1.0), | ||
| Err(LongitudinalError::CovarianceBoundViolation) | ||
| ); | ||
| assert_eq!( | ||
| recover_event_time_lagged_correlation(-2.000_000_000_1, 1.0, 4.0, 1.0), | ||
| Err(LongitudinalError::CovarianceBoundViolation) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn exact_boundary_correlations_are_allowed() { | ||
| assert_eq!( | ||
| recover_event_time_lagged_correlation(2.0, 1.0, 4.0, 1.0), | ||
| Ok(1.0) | ||
| ); | ||
| assert_eq!( | ||
| recover_event_time_lagged_correlation(-2.0, 1.0, 4.0, 1.0), | ||
| Ok(-1.0) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn square_root_scaling_avoids_avoidable_variance_product_overflow() { | ||
| let variance = f64::MAX / 4.0; | ||
| let recovered = recover_event_time_lagged_correlation( | ||
| variance / 2.0, | ||
| variance, | ||
| variance, | ||
| 1.0, | ||
| ) | ||
| .expect("representable standardized covariance should remain representable"); | ||
| assert!((recovered - 0.5).abs() < 1.0e-15); | ||
| } | ||
|
|
||
| #[test] | ||
| fn invalid_inputs_fail_closed() { | ||
| assert_eq!( | ||
| recover_event_time_lagged_correlation(f64::NAN, 1.0, 1.0, 1.0), | ||
| Err(LongitudinalError::InvalidTemporalAssociationInput) | ||
| ); | ||
| assert_eq!( | ||
| recover_event_time_lagged_correlation(0.0, 0.0, 1.0, 1.0), | ||
| Err(LongitudinalError::NonPositiveMarginalVariance) | ||
| ); | ||
| assert_eq!( | ||
| recover_event_time_lagged_correlation(0.0, 1.0, 1.0, 0.0), | ||
| Err(LongitudinalError::NonPositiveEventInterval) | ||
| ); | ||
|
seonghobae marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Trait-plus-state lagged association boundary | ||
|
|
||
| ## Status | ||
|
|
||
| Research boundary note for the Longitudinal Modeling bounded context. This note does not activate a production scoring or estimator target. | ||
|
|
||
| ## Scientific correction | ||
|
|
||
| A lagged covariance divided only by the earlier marginal variance is not, in general, an autocorrelation. For event times `t` and `t + Δ`, a Pearson correlation requires both marginals: | ||
|
|
||
| \[ | ||
| \rho_{t,t+\Delta}= | ||
| \frac{\operatorname{Cov}(Y_t,Y_{t+\Delta})} | ||
| {\sqrt{\operatorname{Var}(Y_t)\operatorname{Var}(Y_{t+\Delta})}}. | ||
| \] | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| When the process is nonstationary, `Var(Y_t)` and `Var(Y_{t+Δ})` need not be equal. Consequently, a one-sided ratio such as | ||
|
|
||
| \[ | ||
| \frac{\text{stable variance}+\text{lagged state covariance}} | ||
| {\text{stable variance}+\text{earlier state variance}} | ||
| \] | ||
|
|
||
| can exceed one and must not be published as an autocorrelation. Driver, Oud, and Voelkle (2017) provide the continuous-time state-transition and covariance ingredients used by ctsem, but they do not print or validate that one-sided ratio as an autocorrelation. | ||
|
|
||
| The unmerged implementation that introduced a public `expected_autocorrelation` API is therefore retired rather than patched with an arbitrary restriction such as rejecting only positive drift. That restriction would still be insufficient for a nonstationary initial state with negative drift because the later marginal variance remains necessary. | ||
|
|
||
| ## TEPP contract | ||
|
|
||
| `longitudinal_core::recover_event_time_lagged_correlation` accepts a lagged covariance, the earlier marginal variance, the later marginal variance, and a strictly positive event-time interval. It performs only the temporal association standardization and does not infer state variance, process noise, or a psychometric response kernel. | ||
|
|
||
| This preserves the DDD ownership boundary: | ||
|
|
||
| - reusable static/generalized-mixed/dependence psychometric kernels remain owned by `ContextualWisdomLab/fast-mlsirm`; | ||
| - TEPP owns event-time composition and longitudinal semantics; | ||
| - measurement occasion, rater, and method facets are not substitutes for substantive event time; | ||
| - callers must assemble occasion-specific marginals from an identified temporal model before asking for a correlation. | ||
|
|
||
| The function fails closed when either marginal is non-positive, the interval is non-positive, inputs are non-finite, or the supplied covariance violates the Cauchy-Schwarz bound. | ||
|
|
||
| ## Regression evidence | ||
|
|
||
| The regression suite includes a nonstationary case with earlier variance `1`, later variance `4`, and lagged covariance `1.5`. The retired one-sided ratio would be `1.5`; correct standardization yields `0.75`. The suite also verifies exact `±1` boundaries, rejects incompatible covariance, rejects non-event intervals, and exercises very large representable variances without forming `Var_t * Var_t+Δ` directly. | ||
|
|
||
| This is intentionally narrower than a state estimator. A future production autocorrelation derived from a specific DSEM/continuous-time state model must additionally identify and recover the state transition, process-noise contribution, both marginal variances, uncertainty, and rolling-origin leakage-safe performance before activation. | ||
|
|
||
| ## Research basis | ||
|
|
||
| Driver, C. C., Oud, J. H. L., & Voelkle, M. C. (2017). Continuous time structural equation modeling with R package ctsem. *Journal of Statistical Software, 77*(5), 1–35. https://doi.org/10.18637/jss.v077.i05 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.