Conversation
Frames the problem space for improving SyntheticDiD validation diagnostics - the gap between getting an estimate and being confident it's reliable. Captures current state, opportunities from the jackknife work, and what a complete validation workflow looks like. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds five post-fit diagnostics so practitioners can follow a credible synthetic control validation workflow without reverse-engineering internals: - get_loo_effects_df(): joins jackknife LOO pseudo-values to unit IDs - get_weight_concentration(): effective N, Herfindahl, top-k share - in_time_placebo(): re-fit on shifted fake treatment periods (sweeps all feasible pre-periods by default; reuses self.zeta_omega per R synthdid convention) - sensitivity_to_zeta_omega(): ATT across a log-spaced zeta grid, time weights held fixed - Trajectories: synthetic_pre/post and treated_pre/post retained on results for plotting and custom fit metrics Retains a private _SyntheticDiDFitSnapshot (Y matrices, unit IDs, period lists, survey weights) on results to support re-estimation. Threads unit IDs through the existing jackknife loops so placebo_effects entries can be mapped back to user-facing identities. Rewrites _handle_synthetic in practitioner.py to reference these callables instead of pseudo-code comments. Adds REGISTRY.md subsection documenting purpose, formulas, and edge cases for each diagnostic. Tests: 28 new tests in test_methodology_sdid.py covering snapshot contents, trajectory correctness, LOO ID mapping, concentration metrics (including survey composition), placebo sweep feasibility, zeta-sensitivity regression at multiplier=1.0, and practitioner snippet syntax. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-only - in_time_placebo() returns a DataFrame with the documented columns even when no feasible fake treatment periods exist (e.g., n_pre < 3). Prior behavior returned a schema-less empty frame, breaking the documented contract and downstream code. - get_weight_concentration() validates top_k >= 0 and raises ValueError on negative input. Prior behavior silently coerced to 0. - _SyntheticDiDFitSnapshot marks survey weight arrays (w_control, w_treated) read-only alongside the outcome matrices to prevent accidental mutation by diagnostic methods. Tests: covers the empty-default-placebo schema case (n_pre=2) and the negative-top_k ValueError path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rage
- Reworded the sensitivity_to_zeta_omega() default-grid description in
both docstring and REGISTRY.md. Prior phrasing ("16x log-spaced range")
could be read as "16 points log-spaced"; actual default is a 5-point
grid with multipliers (0.25, 0.5, 1.0, 2.0, 4.0) spanning 16x.
- Publicly exposed trajectory arrays and time_weights_array are now
marked read-only after fit, matching the snapshot's treatment.
- Added test asserting the exact default zeta grid values (not just
length), so doc/implementation drift is caught.
- Added survey-weighted trajectory test: confirms treated_pre/post use
survey-weighted mean and synthetic_pre uses composed omega_eff.
- Added test asserting the public arrays are read-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Overall Assessment Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
Path to Approval
|
sensitivity_to_zeta_omega() returned a zero-column DataFrame when the resolved grid was empty (zeta_grid=[] or multipliers=()), breaking the documented 5-column contract and any downstream caller that branched on an empty grid while still expecting the schema. Pre-declares the columns list and short-circuits with a typed empty DataFrame when zeta_values is empty; the non-empty return path also passes the columns list explicitly so both paths produce identical schemas. Tests cover both entry points that can produce an empty grid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
- Practitioner LOO snippet now guards on `_loo_unit_ids` availability, not just variance_method. Single-treated-unit and single-effective- control designs legitimately return empty jackknife output; a user copy-pasting the prior snippet would hit a ValueError from get_loo_effects_df(). The else branch now describes the actual requirements. - SyntheticDiDResults.__getstate__ drops _fit_snapshot on pickle so generic pickle.dumps() no longer carries outcome matrices, unit IDs, or survey weights to wherever the pickled bytes are sent. The live session is unaffected; unpickled results raise the existing "re-fit to enable" message from in_time_placebo / sensitivity_to_zeta_omega. Tests cover: - Snippet degrades gracefully on single-treated jackknife fit (exec()'s the snippet, asserts the "LOO not available" message). - Pickle round-trip drops the snapshot and leaves public fields intact. - in_time_placebo / sensitivity_to_zeta_omega raise after unpickle. - __getstate__ does not mutate the live instance's snapshot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
Previous change added __getstate__ to block pickle-based serialization of the retained panel state, but these fields were still declared on the SyntheticDiDResults dataclass, so dataclass-recursive serializers (dataclasses.asdict, dataclasses.fields, dataclasses.replace) could still reach outcome matrices, unit IDs, and survey weights. Fix by moving _fit_snapshot, _loo_unit_ids, and _loo_roles out of the dataclass field list entirely. They are now plain instance attributes initialized to None in __post_init__ and populated by SyntheticDiD.fit() after result construction. The dataclass machinery no longer sees them. __getstate__ is kept as defense-in-depth for pickle: plain instance attributes still land in __dict__ and would otherwise pickle normally. Tests: dataclass_fields() and asdict() do not surface the internal state, plus existing pickle / LOO / in_time_placebo / sensitivity tests still pass against the refactored attachment pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
…ides PR #309 added four validation diagnostics to SyntheticDiDResults: `get_weight_concentration()`, `get_loo_effects_df()`, `in_time_placebo()`, and `sensitivity_to_zeta_omega()`. Because this PR is the first place the guides ship inside the wheel, we want them faithful to main's API at the moment of merge. - `llms-full.txt`: add the four methods to the SyntheticDiDResults Methods line and a short "Validation diagnostics" subsection describing each. - `llms-practitioner.txt`: split the former `SyntheticDiD/TROP` bullet so SyntheticDiD now points at the built-in helpers (with the jackknife caveat for LOO); TROP keeps the generic guidance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
SyntheticDiDResults:get_loo_effects_df(),get_weight_concentration(),in_time_placebo(),sensitivity_to_zeta_omega(), plus synthetic/treated pre and post trajectories retained as public fields._SyntheticDiDFitSnapshoton results (Y matrices, unit IDs, period lists, survey weights) so re-estimation diagnostics work from the results object without requiring the user to re-supply panel data. Arrays are marked read-only after fit.placebo_effectscan now be mapped back to user-facing unit identities;get_loo_effects_df()returns the joined view sorted by influence._handle_syntheticinpractitioner.pyrewritten to reference the new callables instead of pseudo-code comments.Validation diagnosticssubsection documenting purpose, formulas, and edge cases for each diagnostic;doc-deps.yamllinksresults.pyto the methodology entry.Methodology references (required if estimator / math changes)
in_time_placebo()reusesself.zeta_omega/self.zeta_lambdaby default instead of recomputing auto-zeta per fake window (matches Rsynthdidconvention of treating regularization as a property of the original fit). Labelled**Note:**in REGISTRY.md.sensitivity_to_zeta_omega()holds the original Frank-Wolfe time weights fixed to isolate sensitivity tozeta_omega;zeta_lambdasensitivity is not exposed. Labelled**Note:**in REGISTRY.md.Validation
tests/test_methodology_sdid.pygains 33 new tests acrossTestFitSnapshot,TestTrajectories,TestLooEffectsDf,TestWeightConcentration,TestInTimePlacebo,TestSensitivityToZetaOmega,TestPractitionerSdidReferences- covering snapshot shapes and read-only contract, unweighted and survey-weighted trajectories, LOO DataFrame correctness (positional mapping, NaN propagation, ValueError on non-jackknife variance), concentration metrics under composed survey weights, negativetop_kvalidation, in-time placebo default sweep feasibility, empty-default schema preservation, explicit-list override, zeta refit regression at multiplier=1.0, and the exact documented default grid values.Security / privacy