From 0a1a39a6c6f55a0148b508354fcccc6b8136f37d Mon Sep 17 00:00:00 2001 From: Charles Shaw Date: Fri, 3 Jul 2026 17:22:14 +0100 Subject: [PATCH] chore: clean package static-analysis findings --- CHANGELOG.md | 3 +++ diff_diff/bootstrap_utils.py | 5 ++++- diff_diff/chaisemartin_dhaultfoeuille.py | 1 - diff_diff/honest_did.py | 2 -- diff_diff/imputation.py | 1 - diff_diff/power.py | 14 ++++++-------- diff_diff/prep_dgp.py | 1 - diff_diff/spillover.py | 2 +- diff_diff/staggered.py | 13 +++++++------ 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fce8d8d3..f2fa5abf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 county event studies -12%/-13% (rust/python), with no scenario regressing. Rust outputs move only at the cross-backend parity tolerances (REGISTRY-documented; never bit-identical across backends). +- **Internal: cleaned package-source Ruff static-analysis findings** across forward-reference, + stale f-string, invalid `noqa`, and audited unused-local sites. No public API or numerical + behavior change. - **FE-absorption demeaning rewritten: factorize-once + `np.bincount` method of alternating projections** (`demean_by_groups` / `within_transform`). Each absorbed dimension is factorized once and group means are formed via `np.bincount` instead of re-hashing the group keys with diff --git a/diff_diff/bootstrap_utils.py b/diff_diff/bootstrap_utils.py index 5704a2400..9e09036c2 100644 --- a/diff_diff/bootstrap_utils.py +++ b/diff_diff/bootstrap_utils.py @@ -6,12 +6,15 @@ """ import warnings -from typing import Optional, Tuple +from typing import TYPE_CHECKING, Optional, Tuple import numpy as np from diff_diff._backend import HAS_RUST_BACKEND, _rust_bootstrap_weights +if TYPE_CHECKING: + from diff_diff.survey import ResolvedSurveyDesign + __all__ = [ "generate_bootstrap_weights", "generate_bootstrap_weights_batch", diff --git a/diff_diff/chaisemartin_dhaultfoeuille.py b/diff_diff/chaisemartin_dhaultfoeuille.py index 1c675c691..0f54e4929 100644 --- a/diff_diff/chaisemartin_dhaultfoeuille.py +++ b/diff_diff/chaisemartin_dhaultfoeuille.py @@ -5219,7 +5219,6 @@ def _compute_heterogeneity_test( # count, typically 30-300 groups). from diff_diff.linalg import _detect_rank_deficiency - n_params = design.shape[1] rank, _dropped, _pivot = _detect_rank_deficiency(design) # Guard: need MORE observations than rank for a well-defined diff --git a/diff_diff/honest_did.py b/diff_diff/honest_did.py index 624f8736a..2809a2587 100644 --- a/diff_diff/honest_did.py +++ b/diff_diff/honest_did.py @@ -988,7 +988,6 @@ def _largest_consecutive_block(times, boundary_val): ) # Build beta_hat and sigma (diagonal - no full VCV for dCDH) - all_times = pre_times + post_times effects = [] ses = [] for h in pre_times: @@ -2143,7 +2142,6 @@ def _arp_confidence_set( ci_ub : float Upper bound of confidence set. """ - num_post = len(beta_hat) - num_pre beta_post = beta_hat[num_pre:] # Point estimate and SE for grid centering diff --git a/diff_diff/imputation.py b/diff_diff/imputation.py index e363b5817..abfea5126 100644 --- a/diff_diff/imputation.py +++ b/diff_diff/imputation.py @@ -1305,7 +1305,6 @@ def _impute_treatment_effects( Imputed counterfactual Y(0). """ df_1 = df.loc[omega_1_mask] - n_1 = len(df_1) # Look up unit and time FE alpha_i = df_1[unit].map(unit_fe).values diff --git a/diff_diff/power.py b/diff_diff/power.py index 08aae4d21..007a53ddd 100644 --- a/diff_diff/power.py +++ b/diff_diff/power.py @@ -182,9 +182,7 @@ def _build_survey_design(self) -> Any: return self.survey_design from diff_diff.survey import SurveyDesign - return SurveyDesign( - weights="weight", strata="stratum", psu="psu", fpc="fpc" - ) + return SurveyDesign(weights="weight", strata="stratum", psu="psu", fpc="fpc") @property def min_viable_n(self) -> int: @@ -2267,14 +2265,14 @@ def simulate_power( if control_group in ("not_yet_treated", "last_cohort"): raise ValueError( f"survey_config does not support control_group='{control_group}' " - f"(requires multi-cohort DGP). Use the custom data_generator " - f"path for survey power with this control-group design." + "(requires multi-cohort DGP). Use the custom data_generator " + "path for survey power with this control-group design." ) if clean_control == "strict": raise ValueError( - f"survey_config does not support clean_control='strict' " - f"(requires multi-cohort DGP). Use the custom data_generator " - f"path for survey power with strict clean controls." + "survey_config does not support clean_control='strict' " + "(requires multi-cohort DGP). Use the custom data_generator " + "path for survey power with strict clean controls." ) # SyntheticDiD placebo variance requires n_control > n_treated. diff --git a/diff_diff/prep_dgp.py b/diff_diff/prep_dgp.py index 95658b689..13c4e0579 100644 --- a/diff_diff/prep_dgp.py +++ b/diff_diff/prep_dgp.py @@ -406,7 +406,6 @@ def generate_factor_data( """ rng = np.random.default_rng(seed) - n_control = n_units - n_treated n_periods = n_pre + n_post if n_treated > n_units: diff --git a/diff_diff/spillover.py b/diff_diff/spillover.py index 1bac157fb..cd2c2ea95 100644 --- a/diff_diff/spillover.py +++ b/diff_diff/spillover.py @@ -273,7 +273,7 @@ def _compute_nearest_treated_distance_sparse( """ # Imported lazily to mirror conley.py's lazy-scipy pattern and keep # module import cheap when the sparse path isn't exercised. - from scipy.spatial import cKDTree # noqa: WPS433 (deferred import) + from scipy.spatial import cKDTree # deferred import n_units = all_coords.shape[0] if metric == "haversine": diff --git a/diff_diff/staggered.py b/diff_diff/staggered.py index cb2563548..72048040d 100644 --- a/diff_diff/staggered.py +++ b/diff_diff/staggered.py @@ -4062,15 +4062,16 @@ def _doubly_robust_rc( X_ct_int = np.column_stack([np.ones(n_ct), X_ct]) X_cs_int = np.column_stack([np.ones(n_cs), X_cs]) - # Control OR predictions for all groups + # Control OR predictions used downstream mu0_post_gt = X_gt_int @ beta_ct # mu_{0,1}(X) for treated-post mu0_pre_gt = X_gt_int @ beta_cs # mu_{0,0}(X) for treated-post mu0_post_gs = X_gs_int @ beta_ct # mu_{0,1}(X) for treated-pre mu0_pre_gs = X_gs_int @ beta_cs # mu_{0,0}(X) for treated-pre mu0_post_ct = X_ct_int @ beta_ct # mu_{0,1}(X) for control-post - mu0_pre_ct = X_ct_int @ beta_cs # mu_{0,0}(X) for control-post - mu0_post_cs = X_cs_int @ beta_ct # mu_{0,1}(X) for control-pre mu0_pre_cs = X_cs_int @ beta_cs # mu_{0,0}(X) for control-pre + # The full DRDID R-convention grid also names mu0_pre_ct and + # mu0_post_cs. They are intentionally not materialized here because no + # residual, adjustment, or influence-function term consumes them. # Treated OR predictions for all groups (for local efficiency adjustment) mu1_post_gt = X_gt_int @ beta_gt # mu_{1,1}(X) for treated-post @@ -4132,11 +4133,11 @@ def _doubly_robust_rc( pscore = np.clip(pscore, self.pscore_trim, 1 - self.pscore_trim) - # Split propensity scores per group - ps_gt = pscore[:n_gt] - ps_gs = pscore[n_gt : n_gt + n_gs] + # Split control propensity scores per group. ps_ct = pscore[n_gt + n_gs : n_gt + n_gs + n_ct] ps_cs = pscore[n_gt + n_gs + n_ct :] + # The symmetric treated slices ps_gt and ps_gs are intentionally not + # materialized: only control propensity slices feed IPW control weights. # ===================================================================== # 3. Group weights and R-convention means