You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A critical review of the waitlist-crossover/DiD statistical-model family found that the core arm-by-wave model is implemented coherently and is statistically defensible for its principal randomised estimand. However, the family should not receive an unconditional methodological sign-off yet: two estimand/reporting defects, an empirically anchored prior that needs an explicit methodological decision, an invalid current model comparison, and several diagnostic/audit gaps require follow-up.
The model is more accurately described as a saturated longitudinal arm-by-wave mixed model than as a classical difference-in-differences model. The clean causal quantity is tau_t2, the absolute immediate-versus-waitlist arm contrast at t2. arm_gap_t1 is a baseline-balance quantity. arm_gap_t3 and delta_crossover = tau_t2 - arm_gap_t3 are post-crossover associations, not randomised causal effects. Dose coefficients are adjusted observational associations. These distinctions are generally present in the implementation and reports and must be preserved.
Priority findings
P1 — Make “new-child” predicted probabilities and scores target the same population
In src/language_reading_predictors/statistical_models/predicted_scores.py, prob_control, prob_intervention, and ame_prob are calculated from the linear predictor before the child random effect is replaced or integrated for a new child. The Beta-Binomial score simulation subsequently applies the new-child adjustment, whereas the Bernoulli path returns before that adjustment. The resulting predicted_scores.csv therefore mixes population-integrated score simulations with fitted-child conditional probability effects; off-floor Bernoulli output is entirely fitted-child conditional despite being described as a “new typical child” result.
Recommended action: define the target population explicitly and apply it consistently. For new-child output, integrate over the child-effect distribution before calculating marginal probabilities and scores, using quadrature or Monte Carlo. If both estimands are scientifically useful, emit separately named finite-sample conditional and new-child marginal results. Add regression tests for both Beta-Binomial and Bernoulli paths.
P1 — Decide how to handle the outcome-informed intercept prior
In src/language_reading_predictors/statistical_models/factories.py, the pooled t1 outcome is used to construct a logit-scale intercept anchor, and the same t1 observations then enter the fitted likelihood. This is an empirical-Bayes prior, not a fully independent prior. It also means the reported prior-predictive distribution is partly informed by the observed outcome data.
Recommended action: preferably replace the anchor with a genuinely external or weakly informative scale-aware prior. If the empirical anchor is retained, label it explicitly as empirical Bayes in the model recipe and report, explain its inferential consequences, and add a sensitivity fit using an independent prior. This may have limited practical influence on tau_t2, but that must be demonstrated rather than assumed.
P1 — Do not interpret the current DID-007 versus DID-107 PSIS-LOO comparison
The reviewed reporting fit for DID-007 failed the convergence gate with 14 divergences in 36,000 draws. The reviewed traces also had maximum Pareto-k values of approximately 1.135 for DID-007 and 1.152 for DID-107. Ordinary PSIS-LOO is unreliable at these values. The current comparison code in scripts/compare_statistical_models.py checks convergence and row identity before calling az.compare, but does not independently reject or prominently flag a comparison with unreliable Pareto-k diagnostics.
Recommended action: first resolve the DID-007 divergences. Then make the comparison gate inspect Pareto-k reliability and refuse to mark the comparison valid when importance sampling is unreliable. Use exact refits/reloo or grouped K-fold validation if the comparison remains scientifically important. Until then, the available evidence cannot establish whether the period-varying dose model improves predictive performance. The existing conditional-within-child interpretation of this LOO comparison should remain explicit.
P2 — Correct boundary behaviour in cell zero-rate posterior-predictive checks
In src/language_reading_predictors/statistical_models/reporting.py, the zero-rate check calculates the share of replicated zero rates greater than or equal to the observed value and flags either extreme tail. When the observed zero rate is exactly zero, that probability is necessarily one, so a well-fitting cell can be falsely flagged. The reviewed artefacts contain several such flags where the replicated median zero rate is also zero.
Recommended action: use a discrete two-sided tail probability, a mid-p-style diagnostic, or direct inclusion of the observation in a posterior-predictive central interval. Add boundary tests for observed zero rates of zero and one.
P2 — Extend prior-sensitivity checks to variant-defining parameters
The prior-sensitivity routing in src/language_reading_predictors/statistical_models/pipeline.py selects only tau_t2, beta_dose, or mu_dose. It therefore omits parameters that define important variants, including DID-007’s sigma_dose/beta_dose_phase and DID-013’s sigma_delta. The existing critical-review note also records tau_t2 prior sensitivity in seven DiD fits, with especially weak information for off-floor outcomes.
Recommended action: define a per-model list of scientifically central sensitivity parameters and run alternative-prior refits for each. Include the heterogeneous-effect scale and period-varying dose parameters where applicable. Treat DID-013 as a sensitivity analysis only: each waitlist deviation is informed by one t3 observation and must be separated from child heterogeneity, Beta-Binomial dispersion, and measurement noise; it cannot identify individual responders.
P3 — Make row exclusions auditable by reason
The generic subsetting logic in src/language_reading_predictors/statistical_models/factories.py increments dropped_rows for both missing-data exclusions and deliberate design exclusions. For example, the reviewed DID-001 configuration reports dropped_rows = 57, while its detailed contract distinguishes one loader exclusion, 53 design-excluded rows, and three factory missing-data exclusions. The detailed contract is useful, but the top-level field is misleading.
Recommended action: replace the overloaded count with typed, reason-specific counters and calculate any total from those components. Test that the components are mutually exclusive and reconcile to the source-row count.
Refactoring recommendations
Introduce immutable, validated DidRunSettings and DidRunPlan types analogous to the ITT family. Resolve contradictory or unknown options before data loading and output reset, and make that plan the single input to model construction, reporting, influence refits, and metadata.
Split build_did_model into arm-by-wave and dose-model factories. The two designs have different rows, estimands, and assumptions and should not be controlled by a growing collection of spec.extra flags.
Move family-owned construction, estimand derivation, diagnostics, and audit logic from the very large factories.py and pipeline.py compatibility modules into a focused statistical_models/did.py module.
Replace the untyped extras mapping with a DiD result/audit dataclass so required fields and meanings are visible to unfamiliar readers and checked statically.
Remove the legacy delta reporting branch once stale artefact compatibility is no longer required.
Consider using “waitlist-crossover arm-by-wave” as the primary reader-facing name, retaining “DiD” as a historical/search alias. This will reduce the risk that readers interpret tau_t2 as a classical change-in-changes coefficient.
Add one small real-sampler parameter-recovery test for the production Beta-Binomial random-intercept model. Current statistical validation covers closed-form saturated Binomial behaviour and prior prediction, but not recovery in the actual hierarchical likelihood.
Verification completed during review
Focused DiD validation, factory, reporting, predicted-score, comparison, and key-findings tests passed, as did targeted Ruff checks. Those tests confirm the currently encoded behaviour but do not cover the defects above. Thirteen of the fourteen reviewed production fits passed the convergence gate; DID-007 did not. No source files were changed during the review.
Definition of done
New-child probability and score estimands use a single explicitly documented target population, with Beta-Binomial and Bernoulli regression tests.
The intercept prior is made independent of the fitted outcomes or is explicitly documented and sensitivity-tested as empirical Bayes.
DID-007 passes the convergence gate before comparison, and the comparison layer rejects unreliable Pareto-k diagnostics or uses a reliable alternative.
Cell zero-rate PPC checks behave correctly at zero and one.
Prior sensitivity covers all variant-defining estimands and reports the existing tau_t2 sensitivity clearly.
Row exclusions are recorded once and partitioned by reason.
The DiD family has a validated run-plan boundary and clearer separation between arm-by-wave and dose models.
A hierarchical parameter-recovery test exercises the production likelihood.
Reader-facing documentation preserves the causal/associational distinctions above and avoids describing DID-013 as individual-response identification.
Note
Drafted by a LLM-based AI tool (Codex/GPT-5).
Summary
A critical review of the waitlist-crossover/DiD statistical-model family found that the core arm-by-wave model is implemented coherently and is statistically defensible for its principal randomised estimand. However, the family should not receive an unconditional methodological sign-off yet: two estimand/reporting defects, an empirically anchored prior that needs an explicit methodological decision, an invalid current model comparison, and several diagnostic/audit gaps require follow-up.
The model is more accurately described as a saturated longitudinal arm-by-wave mixed model than as a classical difference-in-differences model. The clean causal quantity is
tau_t2, the absolute immediate-versus-waitlist arm contrast at t2.arm_gap_t1is a baseline-balance quantity.arm_gap_t3anddelta_crossover = tau_t2 - arm_gap_t3are post-crossover associations, not randomised causal effects. Dose coefficients are adjusted observational associations. These distinctions are generally present in the implementation and reports and must be preserved.Priority findings
P1 — Make “new-child” predicted probabilities and scores target the same population
In
src/language_reading_predictors/statistical_models/predicted_scores.py,prob_control,prob_intervention, andame_probare calculated from the linear predictor before the child random effect is replaced or integrated for a new child. The Beta-Binomial score simulation subsequently applies the new-child adjustment, whereas the Bernoulli path returns before that adjustment. The resultingpredicted_scores.csvtherefore mixes population-integrated score simulations with fitted-child conditional probability effects; off-floor Bernoulli output is entirely fitted-child conditional despite being described as a “new typical child” result.Recommended action: define the target population explicitly and apply it consistently. For new-child output, integrate over the child-effect distribution before calculating marginal probabilities and scores, using quadrature or Monte Carlo. If both estimands are scientifically useful, emit separately named finite-sample conditional and new-child marginal results. Add regression tests for both Beta-Binomial and Bernoulli paths.
P1 — Decide how to handle the outcome-informed intercept prior
In
src/language_reading_predictors/statistical_models/factories.py, the pooled t1 outcome is used to construct a logit-scale intercept anchor, and the same t1 observations then enter the fitted likelihood. This is an empirical-Bayes prior, not a fully independent prior. It also means the reported prior-predictive distribution is partly informed by the observed outcome data.Recommended action: preferably replace the anchor with a genuinely external or weakly informative scale-aware prior. If the empirical anchor is retained, label it explicitly as empirical Bayes in the model recipe and report, explain its inferential consequences, and add a sensitivity fit using an independent prior. This may have limited practical influence on
tau_t2, but that must be demonstrated rather than assumed.P1 — Do not interpret the current DID-007 versus DID-107 PSIS-LOO comparison
The reviewed reporting fit for DID-007 failed the convergence gate with 14 divergences in 36,000 draws. The reviewed traces also had maximum Pareto-k values of approximately 1.135 for DID-007 and 1.152 for DID-107. Ordinary PSIS-LOO is unreliable at these values. The current comparison code in
scripts/compare_statistical_models.pychecks convergence and row identity before callingaz.compare, but does not independently reject or prominently flag a comparison with unreliable Pareto-k diagnostics.Recommended action: first resolve the DID-007 divergences. Then make the comparison gate inspect Pareto-k reliability and refuse to mark the comparison valid when importance sampling is unreliable. Use exact refits/reloo or grouped K-fold validation if the comparison remains scientifically important. Until then, the available evidence cannot establish whether the period-varying dose model improves predictive performance. The existing conditional-within-child interpretation of this LOO comparison should remain explicit.
P2 — Correct boundary behaviour in cell zero-rate posterior-predictive checks
In
src/language_reading_predictors/statistical_models/reporting.py, the zero-rate check calculates the share of replicated zero rates greater than or equal to the observed value and flags either extreme tail. When the observed zero rate is exactly zero, that probability is necessarily one, so a well-fitting cell can be falsely flagged. The reviewed artefacts contain several such flags where the replicated median zero rate is also zero.Recommended action: use a discrete two-sided tail probability, a mid-p-style diagnostic, or direct inclusion of the observation in a posterior-predictive central interval. Add boundary tests for observed zero rates of zero and one.
P2 — Extend prior-sensitivity checks to variant-defining parameters
The prior-sensitivity routing in
src/language_reading_predictors/statistical_models/pipeline.pyselects onlytau_t2,beta_dose, ormu_dose. It therefore omits parameters that define important variants, including DID-007’ssigma_dose/beta_dose_phaseand DID-013’ssigma_delta. The existing critical-review note also recordstau_t2prior sensitivity in seven DiD fits, with especially weak information for off-floor outcomes.Recommended action: define a per-model list of scientifically central sensitivity parameters and run alternative-prior refits for each. Include the heterogeneous-effect scale and period-varying dose parameters where applicable. Treat DID-013 as a sensitivity analysis only: each waitlist deviation is informed by one t3 observation and must be separated from child heterogeneity, Beta-Binomial dispersion, and measurement noise; it cannot identify individual responders.
P3 — Make row exclusions auditable by reason
The generic subsetting logic in
src/language_reading_predictors/statistical_models/factories.pyincrementsdropped_rowsfor both missing-data exclusions and deliberate design exclusions. For example, the reviewed DID-001 configuration reportsdropped_rows = 57, while its detailed contract distinguishes one loader exclusion, 53 design-excluded rows, and three factory missing-data exclusions. The detailed contract is useful, but the top-level field is misleading.Recommended action: replace the overloaded count with typed, reason-specific counters and calculate any total from those components. Test that the components are mutually exclusive and reconcile to the source-row count.
Refactoring recommendations
DidRunSettingsandDidRunPlantypes analogous to the ITT family. Resolve contradictory or unknown options before data loading and output reset, and make that plan the single input to model construction, reporting, influence refits, and metadata.build_did_modelinto arm-by-wave and dose-model factories. The two designs have different rows, estimands, and assumptions and should not be controlled by a growing collection ofspec.extraflags.factories.pyandpipeline.pycompatibility modules into a focusedstatistical_models/did.pymodule.extrasmapping with a DiD result/audit dataclass so required fields and meanings are visible to unfamiliar readers and checked statically.deltareporting branch once stale artefact compatibility is no longer required.tau_t2as a classical change-in-changes coefficient.Verification completed during review
Focused DiD validation, factory, reporting, predicted-score, comparison, and key-findings tests passed, as did targeted Ruff checks. Those tests confirm the currently encoded behaviour but do not cover the defects above. Thirteen of the fourteen reviewed production fits passed the convergence gate; DID-007 did not. No source files were changed during the review.
Definition of done
tau_t2sensitivity clearly.