From 676da600382f7039ff93f2412bc4abac07deae0f Mon Sep 17 00:00:00 2001 From: igerber Date: Mon, 13 Jul 2026 14:06:39 -0400 Subject: [PATCH 1/5] feat: fuzzy regression discontinuity via treatment_col on RegressionDiscontinuity Local Wald ratio estimand with rdrobust 4.0.0 parity end-to-end: linearized bias correction and delta-method variances (T stacked as a second response column), fuzzy-ratio bandwidth objective with R's sharpbw / one-sided perfect-compliance auto-switch, full first-stage three-row mirror + summary block, weak-first-stage warning (documented deviation - R is silent), and R's exact identification error. 7 new fuzzy golden configs (23 total). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4 --- CHANGELOG.md | 60 ++- README.md | 2 +- .../R/generate_rdrobust_estimates_golden.R | 66 ++- .../data/rdrobust_estimates_golden.json | 326 +++++++++++- diff_diff/_rdrobust_port.py | 472 ++++++++++++++---- diff_diff/guides/llms-autonomous.txt | 18 +- diff_diff/guides/llms-full.txt | 20 +- diff_diff/guides/llms.txt | 2 +- diff_diff/rdd.py | 356 +++++++++++-- docs/api/regression_discontinuity.rst | 54 +- docs/choosing_estimator.rst | 2 +- docs/doc-deps.yaml | 2 +- docs/methodology/REGISTRY.md | 113 ++++- ...o-cattaneo-farrell-titiunik-2017-review.md | 2 +- .../calonico-cattaneo-titiunik-2014-review.md | 2 +- docs/references.rst | 4 + tests/test_rdd.py | 120 +++++ tests/test_rdd_methodology.py | 192 +++++++ tests/test_rdd_parity.py | 69 ++- tests/test_rdrobust_port.py | 180 +++++-- 20 files changed, 1788 insertions(+), 274 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3422dd64..521082da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,31 +8,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- **`RegressionDiscontinuity` - sharp regression discontinuity estimation with robust - bias-corrected inference (alias `RDD`).** Local-polynomial sharp RD per Calonico, - Cattaneo & Titiunik (2014), parity-targeting R `rdrobust` 4.0.0 end-to-end: all 10 - data-driven bandwidth selectors (`mserd` default, `msetwo`/`msesum`/comb and the - CER-optimal variants), triangular/epanechnikov/uniform kernels, `masspoints` - adjust/check/off, manual `h`/`b`/`rho` with R-exact resolution semantics (including - `rho`-without-`h` applying to selected bandwidths and the unconditional N<20 - full-range fallback), and the three-row Conventional / Bias-Corrected / Robust - output. **Canonical binding:** `att`/`se`/`t_stat`/`p_value`/`conf_int` are ONE +- **`RegressionDiscontinuity` - sharp AND fuzzy regression discontinuity estimation + with robust bias-corrected inference (alias `RDD`).** Local-polynomial RD per + Calonico, Cattaneo & Titiunik (2014), parity-targeting R `rdrobust` 4.0.0 + end-to-end: all 10 data-driven bandwidth selectors (`mserd` default, + `msetwo`/`msesum`/comb and the CER-optimal variants), + triangular/epanechnikov/uniform kernels, `masspoints` adjust/check/off, manual + `h`/`b`/`rho` with R-exact resolution semantics (including `rho`-without-`h` + applying to selected bandwidths and the unconditional N<20 full-range fallback), + and the three-row Conventional / Bias-Corrected / Robust output. **Fuzzy designs** + via `fit(..., treatment_col=...)` (R's `fuzzy=`): the estimand is the local Wald + ratio (complier LATE at the cutoff under monotonicity) with the LINEARIZED + bias correction and delta-method variance of rdrobust (T stacked as a second + response column; Y-T covariance via the `res @ s_Y` collapse); bandwidths select + on the fuzzy-ratio objective by default with R's exact `sharpbw`/one-sided + perfect-compliance auto-switch to the sharp reduced form; the first stage + (take-up jump) is a full three-row `first_stage*` mirror plus a first-stage + `summary()` block; a weak-first-stage `UserWarning` fires when the first-stage + robust CI contains zero (documented deviation - R is silent; CCT 2014 Theorem 3 + "guard and warn", Feir-Lemieux-Marmer weak-IV inference a documented seam); R's + exact no-variation-no-jump identification error is raised on both entry points. + **Canonical binding:** `att`/`se`/`t_stat`/`p_value`/`conf_int` are ONE coherent row - the robust bias-corrected row (`att = tau_bc`, CI centered on it, - `t_stat == att/se`), preserving the library-wide field identities; rdrobust's + `t_stat == att/se`), preserving the library-wide field identities; the new + `estimand` results field names what `att` measures per fit; rdrobust's printed headline coefficient is exposed as `att_conventional` with a full inference row, and `summary()` prints the familiar three-row table. Estimation-path port - (`rdrobust_fit_sharp`: Q_q bias-correction score matrix, conventional/robust NN - sandwiches) validated against a new estimates golden - (`benchmarks/data/rdrobust_estimates_golden.json`, 16 configurations incl. the - Senate anchors) at rtol=1e-9 in `tests/test_rdd_parity.py`; R-free methodology + (`rdrobust_fit`: Q_q bias-correction score matrix, conventional/robust NN + sandwiches, fuzzy ratio/first-stage variances) validated against a new estimates + golden (`benchmarks/data/rdrobust_estimates_golden.json`, 23 configurations incl. + the Senate anchors and 7 fuzzy configs - default/sharpbw/manual-h/epa/msetwo/ + one-sided-compliance/ties) at rtol=1e-9 in `tests/test_rdd_parity.py` (+ port-level + linearized-bias pins in `tests/test_rdrobust_port.py`); R-free methodology anchors (CCT 2014 Remark 7 bias-corrected == local-quadratic equivalence at rel - 1e-10 across all kernels, invariances, joint-NaN degenerate contracts) in + 1e-10 across all kernels, perfect-compliance == sharp reproduction, bandwidth + auto-switch locks, invariances, joint-NaN degenerate contracts) in `tests/test_rdd_methodology.py`; API/validation suite in `tests/test_rdd.py`. Deviations from R (each labeled in the REGISTRY section): warn-instead-of-silent - NaN drops, warn-and-ignore `b`-without-`h`, fail-closed targeted errors on - degenerate designs, and the canonical-binding note above. Sharp designs only: - fuzzy RD, covariates (CCFT 2019 - review on file), cluster-robust variance, - weights, kink estimands, and rdplot/density diagnostics are documented follow-ups. + NaN drops, warn-and-ignore `b`-without-`h`, the weak-first-stage warning, + warn-and-ignore `sharpbw` on sharp fits, fail-closed targeted errors on + degenerate designs, and the canonical-binding note above. Covariates (CCFT 2019 - + review on file), cluster-robust variance, weights, kink estimands, weak-IV-robust + fuzzy inference, and rdplot/density diagnostics are documented follow-ups. - **Internal: mypy enforced at zero errors.** Triaged the 184 pre-existing `mypy diff_diff` errors to an enforceable zero and added a blocking Mypy job to the Lint CI workflow (pinned `mypy==2.1.0` + pinned numpy/pandas/scipy for stub @@ -91,8 +108,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - `diff_diff/guides/llms-autonomous.txt` no longer lists regression discontinuity as - out of scope: sharp RD routes to `RegressionDiscontinuity`; only fuzzy RD is - referred to external tooling. + out of scope: sharp AND fuzzy RD route to `RegressionDiscontinuity`; only kink + designs and the covariate-adjusted / cluster-robust RD variants are referred to + external tooling. - **Internal: repo-wide lint normalization + pinned tooling.** black/ruff/mypy are now pinned exactly in the `dev` extra (`black==26.3.1`, `ruff==0.15.13`, `mypy==2.1.0`; the tools require Python >= 3.10 — the library floor stays 3.9); full `black` + diff --git a/README.md b/README.md index 912e0bed..81f54c86 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Full guide: `diff_diff.get_llm_guide("practitioner")`. - [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html) - triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility - [ContinuousDiD](https://diff-diff.readthedocs.io/en/stable/api/continuous_did.html) - Callaway, Goodman-Bacon & Sant'Anna (2024) continuous treatment DiD with dose-response curves - [HeterogeneousAdoptionDiD](https://diff-diff.readthedocs.io/en/stable/api/had.html) - de Chaisemartin, Ciccia, D'Haultfœuille & Knau (2026) for designs where **no unit remains untreated**; local-linear estimator at the dose support boundary returning Weighted Average Slope (WAS) on Design 1' (`d̲ = 0` / QUG) or `WAS_{d̲}` on Design 1 (`d̲ > 0`, continuous-near-d̲ or mass-point), with a multi-period event-study extension (last-treatment cohort, pointwise CIs). **Panel-only** in this release - repeated cross-sections rejected by the validator. Alias `HAD`. -- [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2014) sharp regression discontinuity with robust bias-corrected inference and rdrobust-parity bandwidth selection (all 10 selectors, mass-point handling). **Sharp designs only** in this release; canonical `att` is the bias-corrected estimate with a coherent robust CI (rdrobust's printed headline is `att_conventional`). Alias `RDD`. +- [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html) - Calonico, Cattaneo & Titiunik (2014) sharp AND fuzzy regression discontinuity with robust bias-corrected inference and rdrobust-parity bandwidth selection (all 10 selectors, mass-point handling; fuzzy via `treatment_col=` with a first-stage block and weak-identification warning). Canonical `att` is the bias-corrected estimate with a coherent robust CI (rdrobust's printed headline is `att_conventional`). Alias `RDD`. - [StackedDiD](https://diff-diff.readthedocs.io/en/stable/api/stacked_did.html) - Wing, Freedman & Hollingsworth (2024) stacked DiD with Q-weights and sub-experiments; optional covariate balancing (Ustyuzhanin 2026) - [EfficientDiD](https://diff-diff.readthedocs.io/en/stable/api/efficient_did.html) - Chen, Sant'Anna & Xie (2025) efficient DiD with optimal weighting for tighter SEs - [TROP](https://diff-diff.readthedocs.io/en/stable/api/trop.html) - Triply Robust Panel estimator (Athey et al. 2025) with nuclear norm factor adjustment diff --git a/benchmarks/R/generate_rdrobust_estimates_golden.R b/benchmarks/R/generate_rdrobust_estimates_golden.R index ed629f10..f9f4b0d0 100644 --- a/benchmarks/R/generate_rdrobust_estimates_golden.R +++ b/benchmarks/R/generate_rdrobust_estimates_golden.R @@ -1,6 +1,8 @@ -# Golden-value generator for the diff-diff sharp-RD ESTIMATION port -# (diff_diff/_rdrobust_port.py::rdrobust_fit_sharp and the public -# RegressionDiscontinuity estimator). +# Golden-value generator for the diff-diff RD ESTIMATION port - sharp AND +# fuzzy (diff_diff/_rdrobust_port.py::rdrobust_fit and the public +# RegressionDiscontinuity estimator; 23 configs across four synthetic DGPs +# + the Senate data, incl. 7 fuzzy configs with full first-stage +# tau_T/se_T/z_T/pv_T/ci_T blocks and per-side take-up coefficients). # # Deliberately a SEPARATE file/JSON from generate_rdrobust_golden.R so the # bandwidth fixtures reviewed in the machinery PR are never regenerated. @@ -24,16 +26,20 @@ TARBALL_SHA256 <- "78f0d6b4bdec4091cc8f42f6f1598704747f95926446d3aaee381ea1d613a run_estimate <- function(y, x, c = 0, masspoints = "adjust", kernel = "tri", p = 1, q = 2, h = NULL, b = NULL, rho = NULL, - level = 95, bwselect = "mserd") { + level = 95, bwselect = "mserd", + fuzzy = NULL, sharpbw = FALSE) { args <- list(y = y, x = x, c = c, masspoints = masspoints, kernel = kernel, - p = p, q = q, level = level, bwselect = bwselect) + p = p, q = q, level = level, bwselect = bwselect, + sharpbw = sharpbw) if (!is.null(h)) args$h <- h if (!is.null(b)) args$b <- b if (!is.null(rho)) args$rho <- rho + if (!is.null(fuzzy)) args$fuzzy <- fuzzy r <- suppressWarnings(do.call(rdrobust, args)) - list( + out <- list( c = c, masspoints = masspoints, kernel = kernel, p = p, q = q, bwselect = bwselect, + fuzzy_in = !is.null(fuzzy), sharpbw = sharpbw, h_in = if (is.null(h)) NA else h, b_in = if (is.null(b)) NA else b, rho_in = if (is.null(rho)) NA else rho, @@ -49,6 +55,18 @@ run_estimate <- function(y, x, c = 0, masspoints = "adjust", kernel = "tri", beta_p_l = unname(as.vector(r$beta_Y_p_l)), beta_p_r = unname(as.vector(r$beta_Y_p_r)) ) + if (!is.null(fuzzy)) { + # First-stage three-row block (Conventional / Bias-Corrected / Robust) + out$tau_T <- unname(r$tau_T) + out$se_T <- unname(r$se_T) + out$z_T <- unname(r$z_T) + out$pv_T <- unname(r$pv_T) + out$ci_T_lower <- unname(r$ci_T[, 1]) + out$ci_T_upper <- unname(r$ci_T[, 2]) + out$beta_t_p_l <- unname(as.vector(r$beta_T_p_l)) + out$beta_t_p_r <- unname(as.vector(r$beta_T_p_r)) + } + out } golden <- list() @@ -57,12 +75,14 @@ golden$metadata <- list( rdrobust_version = as.character(packageVersion("rdrobust")), rdrobust_tarball_sha256 = TARBALL_SHA256, seeds = list(dgp_lee_smooth = 42L, dgp_ties_moderate = 123L, - dgp_asymmetric_scaled = 777L), + dgp_asymmetric_scaled = 777L, dgp_fuzzy = 314L), generator = "benchmarks/R/generate_rdrobust_estimates_golden.R", algorithm = paste( - "rdrobust() sharp-RD estimation blocks (three-row coef/se/z/pv/ci,", - "counts, per-side beta_p) for the vce='nn' no-covariate path,", - "complementing the bandwidth fixtures in rdrobust_golden.json." + "rdrobust() sharp AND fuzzy estimation blocks (three-row coef/se/z/pv/ci,", + "counts, per-side beta_p; fuzzy configs add the first-stage", + "tau_T/se_T/z_T/pv_T/ci_T rows and per-side beta_T_p) for the vce='nn'", + "no-covariate path, complementing the bandwidth fixtures in", + "rdrobust_golden.json." ), r_version = R.version.string ) @@ -116,6 +136,32 @@ golden$dgp_asymmetric_scaled <- list( ) ) +# Fuzzy DGP: two-sided imperfect compliance (take-up jumps 0.15 -> 0.75). +set.seed(314) +n4 <- 1500 +x4 <- 2 * rbeta(n4, 2, 4) - 1 +t4 <- rbinom(n4, 1, ifelse(x4 >= 0, 0.75, 0.15)) +y4 <- 0.5 * x4 + 1.2 * t4 + rnorm(n4, sd = 0.3) +# One-sided perfect compliance variant (T == 0 left of the cutoff): +# exercises the perf_comp bandwidth auto-switch (rdbwselect.R:334-346). +t4_one <- ifelse(x4 >= 0, t4, 0) +# Tied running variable variant (2dp rounding; keeps all masspoints modes +# runnable in R per the sharp-golden lesson). +x4_ties <- round(x4, 2) + +golden$dgp_fuzzy <- list( + x = x4, y = y4, t = t4, t_one = t4_one, x_ties = x4_ties, + configs = list( + default = run_estimate(y4, x4, fuzzy = t4), + sharpbw_true = run_estimate(y4, x4, fuzzy = t4, sharpbw = TRUE), + manual_h = run_estimate(y4, x4, fuzzy = t4, h = 0.2), + epa = run_estimate(y4, x4, fuzzy = t4, kernel = "epa"), + msetwo = run_estimate(y4, x4, fuzzy = t4, bwselect = "msetwo"), + one_sided = run_estimate(y4, x4, fuzzy = t4_one), + ties_adjust = run_estimate(y4, x4_ties, fuzzy = t4) + ) +) + senate_path <- "benchmarks/data/rdrobust_senate.csv" stopifnot(file.exists(senate_path)) senate <- read.csv(senate_path) diff --git a/benchmarks/data/rdrobust_estimates_golden.json b/benchmarks/data/rdrobust_estimates_golden.json index 2da7c875..6102953d 100644 --- a/benchmarks/data/rdrobust_estimates_golden.json +++ b/benchmarks/data/rdrobust_estimates_golden.json @@ -5,10 +5,11 @@ "seeds": { "dgp_lee_smooth": 42, "dgp_ties_moderate": 123, - "dgp_asymmetric_scaled": 777 + "dgp_asymmetric_scaled": 777, + "dgp_fuzzy": 314 }, "generator": "benchmarks/R/generate_rdrobust_estimates_golden.R", - "algorithm": "rdrobust() sharp-RD estimation blocks (three-row coef/se/z/pv/ci, counts, per-side beta_p) for the vce='nn' no-covariate path, complementing the bandwidth fixtures in rdrobust_golden.json.", + "algorithm": "rdrobust() sharp AND fuzzy estimation blocks (three-row coef/se/z/pv/ci, counts, per-side beta_p; fuzzy configs add the first-stage tau_T/se_T/z_T/pv_T/ci_T rows and per-side beta_T_p) for the vce='nn' no-covariate path, complementing the bandwidth fixtures in rdrobust_golden.json.", "r_version": "R version 4.5.2 (2025-10-31)" }, "dgp_lee_smooth": { @@ -22,6 +23,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -52,6 +55,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": 0.14999999999999999, "b_in": null, "rho_in": null, @@ -82,6 +87,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": 0.14999999999999999, "b_in": null, "rho_in": 2, @@ -112,6 +119,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": 2, @@ -142,6 +151,8 @@ "p": 2, "q": 3, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -172,6 +183,8 @@ "p": 0, "q": 1, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -202,6 +215,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -232,6 +247,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -262,6 +279,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -292,6 +311,8 @@ "p": 1, "q": 2, "bwselect": "msetwo", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -322,6 +343,8 @@ "p": 1, "q": 2, "bwselect": "cercomb2", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -358,6 +381,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -388,6 +413,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -424,6 +451,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -449,6 +478,295 @@ } } }, + "dgp_fuzzy": { + "x": [-0.78005953230343628, 0.02935278531597052, -0.65279326449550124, -0.61046850505250094, -0.27504489817215461, -0.4749611030358184, -0.60878135454721127, -0.32763689927784934, -0.044631240961697682, -0.87730458812000189, -0.18707352762541174, -0.27568457677434488, -0.94564124721610121, -0.51460647240873969, -0.65462196818138074, -0.60600146690187739, -0.923532289790579, -0.40008321823999615, -0.25101956498486411, -0.46208839735499663, -0.40220972498690044, -0.11376932913159032, -0.2071705487240203, -0.83931826239819407, 0.021757563672782698, -0.24993365013650992, -0.58428160811679353, -0.22692069410936444, -0.45753663902484665, -0.43892956716885667, -0.65761454009474563, -0.79053752919470244, -0.32779189045765689, -0.77520055504913943, -0.92050124421245483, 0.11716910377963519, -0.185271460342466, -0.94377562091283407, -0.45633056405944028, 0.37778312866101027, -0.34625951360806284, -0.58954830907809286, -0.17086067930802984, -0.29109832456493434, -0.086414234185153371, -0.40443817174756291, -0.53851619415951746, -0.099134312142731607, -0.001021729811865324, -0.0073874256521578863, -0.21109039907572757, -0.8151495011938249, -0.47997074747312507, -0.061396217239532591, -0.51818504093816076, -0.34230019157464342, 0.28148176483981358, -0.32441411927929464, -0.10886650078526161, -0.61166111200950979, -0.25714226732198808, 0.10548104616866505, 0.032806432779504791, -0.24288435944638043, -0.64928130573188136, -0.74392366345835548, -0.78585734968483545, 0.4593030106302638, 0.33877007039644469, -0.28728240757086176, -0.28836412092977459, -0.41537629044531366, 0.10015886741867797, -0.88792477301692818, -0.65639863672202559, -0.83553075963300028, -0.36700615046690777, -0.61216628341988688, -0.055852740371845955, -0.19278873087775272, -0.31538219064624762, -0.73172626472961588, -0.2223063461303989, -0.37023296596947397, -0.23995590970306724, -0.37162477727989252, 0.32634957876520398, -0.39621656290415364, -0.60872021798995946, -0.73118833204042177, -0.25662061141540182, -0.4082003565661666, -0.625854811709875, -0.92544476031621314, -0.4090955372853905, -0.20381804311325546, -0.84839288867172802, -0.33407806871332402, -0.31486647049704752, -0.11054254473121183, -0.56076295152296907, -0.84161010765208188, -0.57261898122995514, 0.064695595759155955, -0.25522453493269737, -0.41648737834116156, -0.44923661293833272, -0.16873905521223187, 0.16934973612231152, -0.62030576879735633, 0.36017665332087301, -0.74460099256480716, -0.0042896849912444912, -0.20988795601386456, 0.027704822004972263, -0.11424311664405207, -0.43443389542302757, -0.1906543044053155, -0.67947623901280352, 0.23689832736555538, 0.35233498426275145, -0.39769057436905142, -0.11416775252241851, 0.10336837410995825, -0.30883328364695384, -0.011934768306353982, -0.71891379535284206, -0.15997341727306535, -0.59121072091063265, -0.35056525028942409, 0.22781287220906021, -0.68680508849772415, -0.63873029719729013, -0.66374667160621814, -0.3157749700833552, -0.73042860321517966, -0.66570151076217232, 0.036025725704275313, -0.12374490366804136, 0.37276569207154053, -0.30448462168342405, -0.0020411421023779353, 0.28735553340107312, -0.18618854613697766, -0.83468793206796266, -0.35464441165380334, -0.24797937288899996, -0.74659929117303192, 0.31741936371304158, 0.18067687823166056, -0.28436481413250814, -0.3870108193693188, -0.55277282860800692, -0.36482143743828954, -0.63012985911635222, -0.44664548255637082, -0.42583389181326814, 0.072926343218252532, -0.40331326760729136, -0.19390016882213656, -0.73173060054535632, -0.53773812046480884, -0.6464044742216849, -0.88704318711660679, -0.32606720120599419, -0.30874316085203568, -0.11891429913872265, -0.8385877406369795, -0.033935770517611985, -0.51217399993122892, -0.3844053366647423, 0.45446619506977126, -0.67628451402753686, -0.098948812246051232, -0.40324910878385056, 0.025439934026724842, -0.23008662062573071, -0.70398023285333244, -0.82292089485512543, -0.59731981885789898, -0.55553284527586988, -0.39891403202902065, -0.22265448503837393, -0.63205449796731039, -0.14033378018800702, 0.065445563302172882, 0.054666090230850894, -0.34325824798237836, -0.47443249389323305, -0.71666964326788929, -0.80969961270295321, -0.61550230221999791, -0.38815147132580108, -0.3718940145572448, 0.14696536123011894, -0.047109914103371953, 0.34797431628985875, -0.23244939787748786, -0.3849074063854343, -0.66212398870646316, -0.11726855621466881, -0.43497731522693261, -0.29351582648389452, -0.049363324586590318, -0.37020530675677465, -0.35147292974674904, -0.41015103939179842, -0.43444685819826423, -0.42759960616525206, -0.81645559145760616, -0.44463004976173182, -0.65286933419544613, -0.58223931557475384, -0.728380206628786, -0.2218034935354346, 0.63340161008544116, -0.79453008370750711, -0.28203653735789858, -0.33362498185305778, -0.62077430168597969, -0.60918122867222912, -0.93827626265296882, -0.035010710821640645, -0.14365791987456999, 0.023374872894688092, -0.54553552621255541, -0.69572719133707106, 0.16381212822855784, -0.80735116745276536, -0.52939515605473209, -0.68250436702380513, -0.94283879888361777, -0.62511018529997031, -0.22980427419535054, -0.95628527385916939, -0.16417936843555159, -0.17957428033158551, -0.25349959628780883, -0.48885171846389408, 0.24849732562700599, -0.47281321292478851, -0.70332824277611583, -0.34626942365634461, -0.36564029825405175, -0.70417405799037747, -0.23044933934328948, -0.76854554080906046, 0.025718758224256844, 0.32362971160015364, -0.17216238118376692, -0.54049626342772172, -0.93072490962479015, -0.53143737633336574, -0.084200713742298539, 0.094289450745748571, -0.10528471149432295, -0.19611466908651409, 0.37710572409218268, -0.38156914239987993, -0.11248015544139189, -0.42974411814127911, -0.16261816331023371, -0.086860567229319896, -0.92092645734665501, -0.44010038843019328, -0.65031765626836302, -0.82893543376431655, -0.35742447204493222, -0.42522304167134295, -0.8285415138767821, -0.86346148275772383, -0.62043423680628573, 0.38824514212303463, -0.45694605662521048, -0.71436622649510229, 0.046322143028929341, -0.40013408998759137, -0.81649251544994406, -0.61671755754976898, -0.31312825789357002, -0.77298288355842282, -0.4902777554407568, -0.60763532105072215, -0.012013512876068111, -0.53866462505944201, -0.81353597621407858, -0.41976755510295649, 0.34443383297825481, -0.52574110451931033, -0.31211293514971294, -0.54403680086699502, -0.24869964917088228, -0.64264314397651512, -0.14215377414519947, 0.086836568280642901, 0.14448087464106951, -0.61126211086655524, -0.70123731974698966, -0.50271417489695569, -0.38611216113430824, -0.62081575856201088, -0.57975922652383083, -0.73276274413825182, -0.74713015091484203, -0.4113931900032729, 0.12284832348450991, -0.69013199842500339, -0.43911333825156706, -0.21087727024632419, -0.41675442233329241, -0.3169158643722424, -0.017264206866779763, 0.32642586426374187, -0.21849107805075518, -0.58132199526995731, -0.33170801527853511, -0.46544777920032199, -0.24728993860217163, -0.15794587910394631, -0.17833047375619926, -0.93161692929924034, -0.25199356284227159, -0.23182167241567342, 0.1198042586984096, -0.46169205156647242, -0.23151682155864928, 0.16617331905575861, -0.41726438629098772, -0.44523637009471073, -0.53272868436692689, -0.3980232985667046, -0.4596962242285203, -0.25523710088233942, -0.41444560849423728, -0.063144467553936434, -0.10266888711232869, -0.52350188923626262, 0.064349827475263188, -0.6881564206081221, -0.39089000656762385, -0.78284466810607278, 0.21906177900476553, 0.42715843477659088, -0.53711948137173593, -0.046055893463221254, -0.64003007657821276, -0.96104970887489516, -0.51052292650493536, -0.21363478853203854, -0.9915917224748062, -0.10406615026609634, -0.83223881235700159, -0.65763628563391507, -0.55331487844897875, -0.20143052774598047, -0.37977583627680722, -0.81125590767506228, 0.022647233850746851, 0.048356507190995268, -0.30596779619862546, -0.38035769584204626, 0.24266078152855819, -0.011403389519391682, 0.17407138735646122, -0.95640085283567811, -0.29491230719571182, -0.80070200933558866, -0.69748172248684681, -0.47921187983787106, -0.43421224135941472, -0.25582830536703094, -0.80330714930246472, -0.66511982467153397, -0.18212410464751683, -0.50764549404892245, -0.46051110483812463, -0.74286486303170429, -0.023662665740741606, -0.010523530551177274, -0.66627738211583987, -0.80677099784071094, -0.61705641322916871, -0.48415303287453038, -0.34801719050299862, 0.4425797060957537, -0.61310896730667053, -0.37070132101273234, -0.09026422845981219, -0.17968421868923101, -0.59442985627791867, -0.19483595983061519, -0.5839542362176775, -0.58347598978514315, -0.7122093159006242, -0.42440735142408015, -0.078431453791925398, 0.14325103242121373, -0.11569536962682925, 0.056653069133087541, -0.78922355412005929, -0.30480322961651229, -0.63129894264449171, -0.72193545312022711, -0.76794326483466147, -0.95704858457816899, -0.90346484200799848, -0.15755184837239544, 0.18758764168633868, -0.41354540081860713, 0.12865866102231327, 0.061657724160084859, -0.21263794410701775, 0.29293656520412181, -0.12910869043302098, -0.27286809643546339, -0.32858015492937709, -0.41862657326715391, -0.3616188630979108, -0.20799073282212688, -0.30653133924731635, -0.61817024555382638, -0.72259312724259761, -0.58086993702344936, -0.52154053742697326, -0.86729400230963205, -0.59665278579435377, -0.10456043774075974, -0.21356788377891756, -0.62747798409835309, -0.27157433998034697, -0.7891753461944625, 0.056149618234272891, 0.25969160487323117, -0.13741927195781112, -0.42806908409318678, -0.37214531497162118, -0.74981000229874673, -0.28669541326104842, 0.029579546291025771, -0.66180352127861841, -0.11345060270209395, -0.3991113750518005, 0.20291392423793053, -0.57034884036018496, -0.68969251010000288, -0.48948556521679609, -0.52547828493201176, -0.68231555627504203, -0.44241848339587497, -0.31630794557498276, 0.61537294703223777, -0.75730808794448401, 0.49989468377086599, -0.15485985776892941, -0.42809646915382704, 0.073058457562618306, -0.19931900642401501, 0.4044016342085186, 0.00057703320206914377, -0.43679481238778917, -0.65538116268155122, -0.80344845625239258, -0.84928650777498216, -0.71506088641581389, -0.63661943817108124, -0.49957060319251245, -0.90582097176478948, -0.31133690851918272, -0.63364000999351955, 0.39924869476703395, -0.90798150579233705, -0.70722785100454, -0.52601852287742368, -0.24987182311239353, -0.022040977499224845, 0.28147142991366692, -0.5613382476193578, -0.47110607087517109, -0.57350731051322623, -0.37761723180279871, -0.12353183848361993, -0.27885244293795297, -0.47285876635626489, -0.8097597942712651, -0.82631228784242405, 0.04916344566314601, 0.27701751861921187, 0.080638181116716501, -0.55479249040553336, -0.55197478836627367, -0.76073119357104479, -0.43869541604715501, -0.4773155076389739, -0.46377894233017003, -0.30415508507218481, -0.3950019279283904, -0.77337476393057869, -0.5190759783135136, -0.42811180247221425, -0.032636922859171746, -0.34748000159540959, -0.80960737310268294, 0.0071166228244934349, -0.28284307232562134, -0.025733215572508628, -0.74320486011290154, -0.38896642396069081, -0.70702618100588666, -0.40155262728149677, -0.70703420582625154, -0.69717014206270878, -0.30320438411110429, -0.2698732252418875, 0.015464613764498258, -0.61603154979707742, -0.36972959329939603, -0.47432242986677875, -0.61131038385643244, -0.81898689653874612, -0.30029177704605203, -0.36909671043951309, -0.66247334819999626, -0.56982388462605948, -0.12072620268847378, -0.65137884413308877, -0.10619253883226742, 0.2008401868477987, -0.37628555822481236, 0.66243216885581813, 0.16945798111909394, -0.37871594598597025, 0.33257827580742849, -0.16201527367864166, -0.51574329021288312, -0.68158338653298722, -0.010943162612861657, -0.93015743176317645, 0.08294634960698466, -0.29076896466731217, -0.59777574817549906, -0.057997832299562568, -0.58879191461160874, -0.55791868604287687, -0.10457737111317567, 0.008536939083453321, -0.33656871071322347, -0.92639700157955585, -0.26917215452908783, -0.94348103916696213, -0.22810495704373102, -0.13514274559496442, -0.13729947358823136, -0.56208752617444857, 0.4117916464012128, -0.19147781615848103, -0.21731677273387595, -0.58052258500137732, -0.02544000925546086, -0.33636164066902585, -0.69763897633578442, -0.57486960366261963, -0.1626009924707621, -0.0060602771082112206, 0.054319661307262512, -0.84474699301847067, -0.28905604634327808, -0.28625773698484214, -0.57802606276849544, -0.70897258726484758, -0.59073560560113481, -0.58547055438653428, -0.62054709157578802, -0.27404868154773621, -0.61854867870812635, -0.52991262417740326, -0.2828855793796492, -0.67225862448065066, 0.49022566981602278, 0.27187206601076852, -0.47194110190513516, -0.6073864042455952, -0.79854026915565235, -0.70778513801377185, -0.20448799804330575, -0.74414104469480347, -0.064252616520655503, -0.95375218093883174, 0.3104770090615605, -0.45036391655777186, -0.24671978647807669, -0.42331392840146154, -0.11486964785843645, -0.4171828460678122, 0.21524400201947569, -0.55613421885117598, -0.54582607273113748, 0.19274403435030019, -0.42919520811772749, -0.85345462743614753, -0.86466161203966097, -0.70159747354756419, -0.56032390055111292, -0.96569395447919726, 0.082412664675175051, -0.054502606507924045, 0.1085929174451612, 0.15546597236517523, -0.012384752113471009, 0.49943686462058623, -0.72746881511296779, -0.11179848765749711, 0.0040822008430234646, -0.77028769948334186, -0.47150352550508112, 0.33883994697653019, -0.80597167344630538, -0.66867719248387925, -0.61798116352108678, -0.85412486952084898, -0.1256443567888883, -0.079069579966772996, -0.23892139058313566, 0.077313081574070575, -0.25776031343073691, 0.11110607554090879, -0.16098013054469995, -0.31251158071132823, -0.90448102498908922, -0.2389393784011381, -0.25499707695513341, -0.45216660446597334, -0.17326917329880587, -0.63592603702476624, -0.73067757300640923, -0.51648202532727272, -0.31640224257435079, 0.24550257741364567, -0.244589380339018, -0.47740483956487045, -0.041269121222529015, -0.3423808647495441, -0.20769433393023595, -0.93608646755351066, 0.42637369709567041, -0.86585141711656866, -0.51864758741276651, -0.35869576786140323, -0.15352973004457837, -0.85554810359127109, -0.63560922840848577, -0.49450387388522499, 0.098472131620265024, 0.0040410818742899757, -0.20327538841711934, 0.0086526267329263273, -0.70765147917427007, -0.56737309087273236, -0.51948521281494864, -0.1911743578212789, -0.30597565627400936, -0.63137146967754987, -0.87154017455492017, -0.13418636998073885, 0.090886066033503798, -0.65487708028050784, 0.16257967167260023, -0.046142030303682802, 0.29074316677970602, -0.1827938425220148, -0.67498961766063292, -0.77049442410152758, -0.11932837522519268, -0.71993125767992561, 0.75492997519377836, -0.8062395736172252, -0.63188849175021633, -0.15177677407751389, -0.31602181506431104, -0.58867462813735627, -0.092757360624040941, 0.14554051174597959, -0.25899589670147638, -0.14556635289144326, -0.40520922438739126, -0.6407569786188696, -0.45974257470737456, -0.43180356540901854, -0.40564293851585964, 0.13726332574481104, 0.12540727498411797, -0.34806154808294887, -0.68619047252129151, 0.14366151763173418, 0.18265827725771855, -0.28700375190627403, 0.30919098617061702, -0.56104110914456462, -0.59111131115411308, -0.51677152750220812, -0.034128330783788741, -0.11203887918796762, -0.28070143056020957, -0.43367028771245997, -0.41572064438785494, -0.31503438360752833, -0.58458668176673312, -0.075238076274927956, -0.8191323581889709, 0.26829739827125643, -0.47162483377592301, 0.12563585419313283, -0.18293762744005682, -0.77457715532207838, -0.0053480942702491818, -0.15331146030726817, -0.45630413172983875, -0.29418509603417464, -0.59957885200142935, -0.2205000364480364, -0.37478246614394528, 0.25542123364256719, 0.023905460786373345, -0.36913166538655828, -0.69636654946683096, -0.73074274196491884, -0.027865655370860831, -0.56548063721722353, -0.19518797820632006, 0.18146998388794144, -0.93445885093295633, -0.58437750861927817, -0.30324265165757258, -0.53180109308328849, 0.084092134484406778, -0.75266926407915191, -0.52180689212920583, 0.010589123310857707, -0.25172136854898908, -0.142660037547368, -0.8349611196611304, -0.91010645221304354, -0.25778042291664616, -0.61293538315671692, -0.67939596635521948, 0.015378116794241725, 0.43708055722715677, -0.40371604459565047, -0.042451849094447436, -0.85236896577203691, -0.78504151256059229, -0.68616978922006377, -0.48334458666934188, -0.72581844930878958, -0.070557383133120877, -0.0035016980708066914, -0.59893358141232245, -0.054581402206324903, -0.65953735319558993, -0.22579575448952993, -0.17672094835995678, 0.17612799977887517, -0.70562649729390281, -0.78046803452538871, 0.41472437039903109, -0.17184378659177779, -0.23758182142383411, -0.65415306734440848, -0.74611057100330369, -0.28977401251613999, 0.01755133986236701, -0.18534123177081785, -0.23472998203247519, 0.28323341446806838, -0.48203398971122446, -0.82671898708242486, -0.23026994221608688, -0.36127395775083049, -0.12570659712476517, 0.17488936326764959, -0.82396293992904646, -0.53634670062724088, -0.28651528464151832, -0.29261548414644656, -0.31642127220431215, -0.29660321100184628, -0.016072257509433752, -0.13785743041346588, -0.33420669848857831, -0.49042604047636429, -0.56882883326436318, -0.36206552886703514, -0.49431591699059152, -0.51861346574112244, -0.62254425846247141, -0.36522974687259979, -0.77556462935890336, -0.0029777552090850534, -0.24790841091910942, 0.44016645279273647, -0.33639507399870372, -0.92541026904102619, -0.62813035990156485, -0.2671666351326587, -0.5950583971658594, -0.68152068994619852, -0.71206006450671466, 0.24298086765168514, -0.69102693848825691, 0.2398388482246312, -0.15505480653325987, 0.49679119252644988, 0.21767912092938979, -0.68484743394444969, -0.48296588661179896, -0.79984479613259307, -0.11247262291184279, -0.75348514066632077, -0.17164749443473426, -0.23645865972155711, -0.49424344537729226, -0.65530342401794495, -0.61668739481306867, -0.4060657256068213, -0.41940736432617265, -0.15870031512927263, -0.39857776735681028, 0.15444663305499629, -0.42710419907976382, 0.015331136035666049, -0.49750048479557096, -0.42671669009182922, 0.033483000077262792, 0.1690693000607093, -0.53322636117858058, -0.036709296101262612, -0.27873606360185321, -0.38548733894208675, -0.31668902766860396, -0.13946624936700347, -0.37862645044376342, 0.58987074681123075, -0.61390099843918822, 0.19151353291871054, -0.92974453740677321, -0.39514190984291608, 0.13839707829207182, -0.74671198398398153, -0.046562820568681329, -0.21223948540151416, 0.23281093756074167, 0.21435506071383736, -0.47543172585021065, -0.63313430045508479, 0.42669836405629868, -0.60472463062797099, -0.69180699768834508, -0.48036393518610176, -0.39374509870573904, 0.049022935954735392, -0.68284940535820793, 0.028249477598994766, 0.0023935370919148102, -0.42244513723576937, -0.55548790130012393, -0.73617991613415545, -0.30975901653240145, -0.4867403405007753, -0.78708973015690598, 0.22025729816619166, 0.098599810022844236, -0.5077780980556903, -0.21419781681271921, -0.75421755092077924, -0.8244107772366871, -0.31398502999329447, 0.42025832709560662, 0.11697232562013826, -0.78944665779536893, -0.82034545736288611, -0.46122811232985139, -0.43705078886863069, -0.40023216227533087, 0.13625468996331591, -0.52616878722779226, -0.53374155877226914, -0.55976404396015178, -0.021029872447612452, 0.21126786621029203, -0.36080885642254223, -0.72849306468063468, -0.78042007520338896, -0.67975908965062426, -0.81372839662595564, -0.49855338674118443, -0.53863259365211946, -0.49411998808631674, -0.46838041334319946, -0.45571062483177205, -0.2099047678630932, 0.41419253856968785, -0.20025060968299135, -0.54426046113640214, 0.19856291495056166, -0.36898227028706465, -0.43278822541066697, 0.36856808007461095, -0.8579180187559049, -0.019153352792483092, -0.56126959520361885, -0.79104965200669741, 0.51979083836493922, -0.78823773883141313, -0.85761470361892012, -0.7902173324707733, -0.0059864826965083662, 0.084771663767995165, -0.63330294624521322, -0.68322333696774451, -0.26561027321357566, -0.16449872290906065, -0.68201380158172875, -0.040833603203777269, -0.4582679303568663, -0.74318544981268386, -0.30146202970228975, -0.57080429542599309, -0.82625452769192742, -0.3738217200623466, -0.72471389356331273, -0.37429704186334778, -0.67066926637213997, -0.46249322018304917, -0.044014799432267715, -0.1688751959149396, -0.045776235198932258, -0.3174521447316393, 0.30609540115678979, -0.67752209898620408, 0.11826686113320584, -0.52283777648501539, -0.81132074294329892, -0.060274991575129833, -0.012268790877585811, -0.69674476926541296, -0.073077324245135356, -0.83094599670695934, 0.15526381992970073, -0.2965862476842297, -0.38219756946334549, -0.44102449886801265, -0.26987724581152106, 0.15977298722102518, -0.0087557974820759998, -0.27442616286701182, -0.49084082192037082, -0.59492533615781218, -0.91676965507830543, -0.7273374434958404, -0.34928158089763961, -0.085810615002199442, -0.0083421918170479836, -0.34258648859806629, -0.40928649305741693, -0.59179815988527351, -0.062160215760632465, -0.45129836678884638, -0.39752977798207523, -0.43033492133582629, -0.31687429388573685, -0.69770380795160181, -0.72661919492229476, -0.91261503912418118, -0.087259913039902659, -0.51951947519384567, -0.28725580903088954, -0.22839203374460781, -0.92956162153830746, -0.0055360645483502058, -0.18541203640607584, -0.19099320982702694, 0.019517809802638064, -0.9186550649032319, -0.90749101250504416, 0.025072426384215474, -0.82470529726521136, -0.29126141519169291, -0.19284236242666908, -0.24237016304642234, 0.1698016473907269, -0.5402932832333367, -0.36018657024925393, -0.17334536234518871, -0.53761543217355623, -0.097611742251309863, -0.60136261954600334, -0.28814073077765645, -0.3701820010953718, 0.25739122251897961, -0.85257021244932929, -0.51341881469877837, -0.79793405515756433, -0.12930254022904775, 0.30326594995938638, -0.66616410820494232, -0.82896749484239507, 0.049869829789075837, -0.048061016808835566, 0.4771168192411126, -0.44860081890876258, -0.39385687895544597, -0.32259587593383343, 0.16902464239264869, -0.47502219350992703, 0.19994795439886937, -0.41490524217860225, -0.71505426129361593, -0.48872525071721717, -0.26320724671702578, -0.2425912853634814, 0.090879969897984969, -0.83807165864896294, -0.042912783499034823, -0.88737289814383902, 0.13715771831763246, -0.44615493593186106, -0.0011060231806754306, -0.47721910297014725, 0.3269049964817754, -0.73928838259785989, -0.52876591423444075, -0.68691777264706033, -0.072000986473236606, -0.036386033392673434, -0.26300579164774684, -0.56256637922919239, -0.5028936986437198, 0.75163495080675724, -0.7412569353894396, -0.8667296749990443, -0.79546493524761241, -0.58535252478864108, -0.13176625830143296, 0.24820938218549893, -0.68728225988555702, -0.69954390214719608, -0.71224402155174871, -0.74858421814698772, -0.56641748971347727, 0.069403567632871699, -0.28771423923770401, 0.60949174383318594, -0.075935221021474208, -0.63265107622726868, -0.55906274971127412, -0.75884257107014619, -0.60485930639176222, -0.13152837512556825, -0.73448745462546183, -0.49842115149107957, -0.74680483838585876, -0.14820747724307026, -0.65954694684540949, 0.50935243804130503, -0.41811870017378638, -0.25944401574256126, -0.23359775589009379, -0.49788363233025446, 0.081247643684753124, -0.42766950575292417, -0.6082099420493694, -0.5033556062953326, 0.11418335230284682, -0.76367904271730436, -0.25876980467381783, -0.25650458153489464, -0.65265261030010002, -0.47010334660119657, -0.82548341734202557, -0.32392025752384213, -0.71296525350300455, -0.45984029117747693, -0.17737194119571587, -0.26646647391551281, -0.78099518133678791, -0.83060186673182301, 0.34368332231422483, -0.4100425517939883, -0.39433687209570523, -0.44088394221723348, 0.26044748328706158, -0.87902382055983352, -0.90009527014817459, -0.69102416773737185, -0.50983615036734742, -0.49860975082428072, -0.96184054024310273, -0.68757074247765149, -0.65210489237030345, -0.48694218067142891, 0.056525840671500349, -0.45953198907642512, -0.76777853375944405, -0.042198502688317596, -0.58525506107210756, 0.49626234148285908, -0.61427220403370875, -0.54448867943074075, -0.48890162747807786, -0.64299169906955789, -0.53945720628643834, 0.41696561169898616, -0.6297006817191152, -0.16113361994670083, 0.018925428097193997, -0.49629561584904458, -0.16589600094021006, 0.15719633962554624, -0.81770381985955443, -0.89360560179859083, -0.46746657926010282, -0.66851675760477169, -0.31201218600945091, -0.29087399499287636, -0.046014025031608674, -0.74636429248796432, -0.21689711871026551, -0.44752364134881106, -0.45805790117589007, 0.3613608021093615, 0.12980112300883295, 0.13532034106480562, -0.15853079049188723, -0.61662876001328604, -0.55119972636680714, -0.4414494628481479, -0.27527244205187895, -0.37009400221199773, -0.60061067311921024, 0.070134091121316855, 0.82515321404973418, -0.89486027885587927, -0.42501749593683535, -0.58420967270202895, -0.63786440282044976, -0.64617780404420544, -0.39754088644548258, -0.5820581185491156, -0.18791812637672956, -0.54448297693478209, -0.63291692185761617, -0.35422814679121317, -0.074817466890648276, -0.35674312717471757, -0.77240643430638423, 0.075526117323100106, -0.51172657975697899, -0.58412017563733221, -0.091718705592402472, -0.35604372143405383, -0.43268843269788249, -0.74719720986711202, -0.24946666614375801, -0.65651466381229184, -0.33872557561444816, 0.097200497583904255, 0.30704383438811633, -0.21469303277525942, -0.61996711856741682, -0.17451037208199538, -0.71281812612874862, -0.66811466172066369, 0.110792893022158, -0.52553172807762283, 0.029184371586399749, 0.42882711800113227, 0.067557675682197615, 0.13153732015816155, -0.3397724999790307, -0.63890339261169671, -0.62358273715465784, -0.57009294707777514, -0.011289375377881328, 0.36686801457261509, -0.53051722027214798, -0.61611409258181049, -0.041582234070213331, 0.16012831032326513, 0.08444010260784518, -0.45753758574964876, -0.60121861250993924, 0.12721091599604684, -0.7964978620443619, -0.42314420185978452, -0.42265055462477874, -0.34259127365806441, -0.24498284476044951, -0.58901041536854715, 0.20629550663069263, -0.006608666181223688, 0.63026403980126244, -0.85458062619167874, -0.44979577422115979, -0.045581282789833422, -0.79613246668498139, 0.25724290631227609, -0.82384106642691868, -0.35722074614717447, -0.5074812084116016, -0.22481489353902917, -0.14235060161123037, 0.3001297871524562, -0.47709993194073086, -0.38484922868443217, -0.06423339806834627, -0.81263890031996222, -0.81003189944922982, -0.33105326469468221, -0.30475057360659019, -0.39109395131895031, -0.3054666014872004, -0.11367950259017967, 0.18328071674779056, -0.11786323543662358, -0.078031559963365749, -0.49198464568808575, -0.42851402857432797, -0.65844905706360413, -0.94114268916648713, -0.40573941329525698, -0.63568672463885711, -0.87292871602542321, -0.38961549650665483, -0.41523076305370921, -0.18268845316187388, -0.22284183103740363, -0.40169001814605099, -0.48806717355427065, -0.85418921758471589, -0.16966503204616745, -0.53409668727027337, -0.59594975388281668, -0.30546881942283044, -0.31684617356833122, -0.77929784755808718, -0.62823418643036444, -0.2053615564037764, -0.11492054140960495, -0.12124423997510325, -0.53682110345061662, -0.32989304269498332, -0.43380659491990359, -0.70469823458286196, -0.17294915937693489, -0.95338878530201709, -0.1584194660226862, -0.75846203710820626, -0.73079064837636154, -0.40609599370384597, -0.29833364161847775, -0.16610154061443705, -0.61571291011776685, 0.10546316579815418, -0.067440766152845555, -0.52394871253310615, -0.88506803311116544, -0.78060313977466023, -0.35709967252160313, -0.4233752110174489, -0.43566688150758681, -0.52941729163799756, -0.21890101214839419, -0.079376969393265218, -0.23382077086677444, -0.3856870632206455, -0.77723234937035091, -0.88831816919075157, -0.65781496430857866, -0.5912200748796812, -0.31125212112624201, -0.75752293930506687, -0.73718996866774744, -0.11219457312086378, -0.7067149152612906, 0.19646211643328826, -0.51599555599129632, 0.48739847008002202, -0.079816774126517553, -0.13415971067005361, -0.18165172982259026, -0.69621815489814964, 0.02342549275266137, 0.3123890619959464, 0.55029495149472374, -0.58736187693143194, -0.58086588752731894, -0.42087314675322518, -0.87481485900257605, -0.25177294418545748, -0.14958192536237302, -0.41493677322020439, -0.094389417856660995, -0.21655285353457532, -0.60478166330330463, -0.64353386016507974, 0.046992497885346429, -0.014498695414005836, -0.43518886788769195, -0.11725685535902897, 0.16951238791740164, -0.21638756512301638, -0.26467167572455208, -0.92015979996242403, 0.14244389183592565, 0.68605607721804684, -0.010116750031488708, 0.1282355261231658, -0.23067350354349381, -0.04013326026310593, -0.55574769835239168, 0.040610784974586922, -0.68455778730261574, -0.77564678012578492, -0.14827983588042137, -0.68285652198913838, -0.36528625567340689, -0.38449706386733973, -0.083355756659182489, -0.31621981553515877, -0.45454428312580208, -0.20134071112447338, -0.54516183135014451, -0.93332469093222481, 0.37524103888398153, -0.13549053672377054, -0.035001140436966671, -0.39323759109339873, -0.43205237460414758, -0.72147088652306146, -0.83193290217580174, -0.10165644452189737, -0.43250108722888425, 0.090279153115036648, -0.28492146541671526, 0.18154331164784399, -0.46264677625873374, -0.19673645524182071, 0.47850032866581205, 0.12874388151210137, -0.83086817366153165, -0.088136098989999856, -0.20605962866718286, -0.019662741935159045, -0.89650398285267352, -0.16033241830983369, -0.21023091785036963, -0.086002566102313049, -0.41673726107984954, -0.14455210480169212, -0.63703722296566889, 0.53021939211598057, -0.55053848491263424, -0.0085197657706147512, -0.53580985869755215, -0.29345317723149611, -0.63657083057795316, -0.59980262608543822, 0.044579373329364458, 0.099000075365523355, 0.036210310000086121, -0.044515627390939505, -0.49361209488823155, -0.40239210146063886, -0.35946982661431881, -0.82196662458974246, -0.015224020271977667, -0.52676430303757871, -0.59563554947844122, -0.02519509969245104, -0.26805667141028855, -0.60812320426191424, 0.35212065091890632, -0.25909727815553374, -0.32752038521374582, -0.44104435019148391, -0.72741232054315519, 0.16975786975883844, -0.54897108156005836, -0.17401637069046916, -0.23871996111448601, -0.36576796249006194, -0.097663866018472212, -0.292638823036938, -0.34796817657207313, -0.11460722806164636, -0.94306630640718969, -0.04423051468513528, -0.6394659216584252, -0.3367196717175901, -0.30369812210308345, -0.92319054842225701, -0.80511698058674908, -0.73234401189162057, -0.41656968318379628, -0.67507684053952155, -0.7415781678304787, -0.59636926539840496, -0.74271327894270045, 0.51166892960497012, 0.13861028874803138, -0.79833022025356382, -0.29083259108469683, -0.64274021816629734, -0.24209756998219367, -0.92787915256568709, -0.4654237854097969, -0.94006143456800895, -0.88690288983998322, -0.71107859357232228, -0.34361132155644381, -0.90443813295373188, -0.22528319264299801, -0.3276002733973854, -0.48605782383612572, 0.058630368635778085, -0.45255243362382691, 0.24859685602392223, -0.75813385693481417, -0.11934398200976082, -0.54605082803858573, -0.064072217444271606, -0.79540350425694006, -0.23991673524313173, 0.2168597017905034, 0.39237088197492054, -0.53457613803668058, -0.42046094806668988, -0.41192178758966247, -0.019578611918649846, -0.77770984848349312, -0.12677942840273415, -0.23473665530409304, -0.28229528880921151, -0.40637387048407414, -0.55102858392647891, -0.47122982088015408, -0.41324998689156012, 0.38833370614354012, -0.90415457675667599, -0.10020577374601225, -0.53839433041160256, -0.42801391107275844, 0.47677773717518246, -0.19156976346164967, -0.82359585468666618, -0.1203241967623695, -0.36571496388006886, 0.075978779826720855, -0.036441585619217176, -0.16781663801137192, -0.35181872249878055, -0.83741854217386114, -0.5661810504389172, -0.16154234490842001, -0.70367753815610845, -0.24950202811261413, -0.40204702820165827, 0.20622823007828162, -0.38343399780059129, -0.26794923544129423, -0.57929391617288795, -0.46413788476893703, -0.51885388202710336, -0.10569669014812744, -0.010193018367747664, -0.075987907856211123, -0.51193629643443539, -0.53434439305513004, -0.79337642819728382, 0.47916165797659671, -0.37984811212977021, -0.90054805064418697, 0.35199426891992713, -0.18329131891124428, -0.54666963154287562, -0.61270055251005939, -0.085992446351098151, -0.41191621908458598, -0.18440546514793454, -0.53647923608107417, -0.83016659160998008, 0.14775209508919174, -0.26359844624828888, 0.18131784104883142, -0.48536500836721153, -0.54722416070144786, -0.29774942708099017, -0.2108841852424056, -0.63167626454948633, -0.46590508730568092, -0.57816961515622811], + "y": [0.17825636799189792, 1.509204379359713, -0.60799857298429028, 0.013630790765705336, 0.010299744162157037, -0.59441791599720228, 0.92609499749419599, -0.55351084166610343, 0.36320209903180395, -0.59332105501568333, -0.032106271760210595, 0.12856428314550172, -0.6559005058978864, -0.040982103418308768, -0.38490370361546111, -0.21541540367863987, -0.10448867881530305, 0.0035672507600658931, -0.076007912363952504, 0.14375675205768274, -0.50213353680866024, 0.076215449763561399, -0.7137026055481831, -0.47479522414044328, 1.2402262302550051, -0.47509805324191762, -0.033987682097264049, 0.061915465213342125, -0.35751499340392123, -0.19469257710900251, 0.84266205426600627, -0.58735337871494564, -0.1833791975462539, -0.071125824752126199, 1.125484537300707, 1.1371151338989895, 0.22449865702193278, 0.23489851049566474, 0.3294719933401084, 1.853532806199929, -0.27449980011351793, -0.56552367681649118, -0.014789741765518671, -0.64420491520313683, 0.34123809772915931, -0.21940363439578109, -0.43004824366731431, -0.30633858509073547, -0.034478113141999417, 0.3379640347889839, 0.30737863877650456, -0.77419335207081497, 1.0066728228646908, -0.050573211590671949, -0.054635056427195017, -0.30874311367422969, 1.3676431822594595, 0.30853648378361165, 0.45735225387289236, -0.38932423768861174, -0.34425192936539739, 0.20634053610618378, -0.50329791015367942, 0.026577720034986763, 1.1480859472671396, -0.038473916282335707, -0.8970609394590352, -0.10276785817615813, 1.1876032558664005, -0.25158028126912929, -0.33884355124832788, 0.69021097094880934, 1.8391895587087712, -0.14599728561754877, -1.0478869522726129, -0.29928735689002567, 1.0589945686014013, -0.55000765059587442, 0.32789765088212225, 0.4545824121071228, 0.3672615658529606, -0.14664932746052017, -0.24091099923633785, 0.31276969394397558, 0.086780958363906341, -0.47436939384589721, 1.4637143066376024, -0.35448202925521571, 0.03134012398402114, -0.058298269018918158, -0.16606025438019056, 0.067849862919412418, 0.33731203016982891, -0.31237795936399171, -0.23323347502356817, -0.45292585777355843, -0.53239586060756627, -0.26472131562834433, -0.086065666047276293, 1.1431895975473412, 1.0087269427748635, -0.49094605424824034, -0.20199070828225529, 1.0425783091077137, 0.14073695423992266, -0.071302044725928604, 0.079445616197185431, -0.11132300488084326, 1.0332445225793023, -0.64368525737399984, 1.4580280267711008, -0.54675442257349915, -0.22932533712249564, -0.21187216227899747, 2.2401050505148348, 1.2398021880358256, -0.31001907235037585, -0.46877168896925109, -0.17796140666778573, 1.4108795442958262, 1.8074927160579537, -0.19262464363020368, 0.26368530573793786, 1.6750232999388772, 0.0081605253464284155, -0.19529524934033982, -0.48332477422433007, -0.083599537608208346, 0.77315733920918606, 0.20387410308686532, 0.29038462937282894, -0.0070678513403955034, -0.13988018933364513, -0.08937089948630339, 0.071917879251848948, -0.20230339277038256, -0.27592571100745311, 1.5008637147168453, 0.28402750359525025, 1.7052364817735681, 1.6302063354013194, 0.083502258148172262, 0.26613103363267143, 0.13143269999604867, 1.2966542469319684, -0.15415293435930821, 1.5948989693922582, -0.23239280293693876, 1.4492543886037297, 1.344126295028397, -0.67697619812708598, -0.35593346917220148, 1.2321032387349891, -0.55357097910663577, -0.45061029339656827, 0.34979320614476039, 0.28957188111850246, 0.83934866307852329, -0.46829860204138535, 0.17287497588649098, -0.10570709407558448, -0.054827920575717365, -0.16892766807255513, -0.26994938246550398, 0.032532870906953365, -0.36065341622380742, -0.78188773639488729, -0.60724773457019587, -0.13532420263544209, -0.40660469719020442, -0.11022843985597416, 0.17287078197723166, 0.78780825937398791, -0.060016654154789531, 0.095444584281438871, 1.3435710620117354, -0.28501260699360342, -0.97509794148660178, -0.89034331181774107, -0.51306580812970837, -0.3175631400588631, -0.18812700720191297, 0.073708244707974518, 0.69401975684771744, -0.16964234897011901, 0.28396324247732602, 1.0549914034804702, 0.2156859917302516, -0.21853635811864258, 0.93942404735070695, 0.62457317872767293, -0.19546701087159973, -0.4652295804686134, 0.52544879768904784, 1.2932386934846909, 1.1212042662916775, 1.4727721254557387, -0.34435688746869397, -0.094232785468275071, -0.54343823693625837, 0.079892859429166296, 0.11116463443018054, 0.52071499235457841, -0.29092774827662982, 0.16826457905556391, -0.040668661699701153, 0.0026388337497548486, 0.085655522478742918, 0.47369451333647228, -0.095757377508194186, 0.058012501672765282, -0.63124098933267947, -0.25560720410374826, -0.70201626973350661, -0.33619194078965287, 1.2961644263065235, 1.0588990821730762, 0.31705658421902749, 0.35119308006501054, -0.66777593339557484, -0.045971147334035734, -0.87369914096058698, 1.1284941694158295, 0.13860261573453703, 0.2059499447127105, -0.30080057530879284, -0.11452025612646152, 1.2171596338493915, -0.39859874791917882, -0.11321097449292558, 0.31639695813589319, -0.77113220005312189, 0.87882938329017846, -0.34652191876267424, -0.93181339715883171, 0.17255858162639887, -0.1076074978787096, 0.14616090567959389, -0.21844485589768015, 0.1446908421530898, -0.25552811354021565, -0.25307190584875072, 0.12904735357589975, 0.27662628855814919, -0.66588964956570607, -0.041656455057228522, -0.57001332667281246, 0.90280702727587792, -0.074870181672934744, 0.95089442833045035, -0.83756408372103608, -0.11729535533875124, 0.21830849327464219, -0.48670125145764492, 1.3299866157128428, -0.25907935793205694, -0.2459053561887955, 1.2436209712553732, -0.40040397451132259, 0.12198790159602699, -0.18126123692439505, -0.13609759994484882, 0.4474059961613539, -0.13397197791306814, -0.11626460839774373, -0.1232134103138002, -0.32505556015339881, -0.10437979986559297, -0.0023352922205254756, -0.57609637146557169, -0.45266386527274094, -0.13834962130473494, 1.4882926660779792, -0.64129482506682844, -0.9051927942945881, 0.88470348133789556, -0.37646844614213815, -0.090575660065036423, 0.61955289919514844, -0.058459082411569205, -0.20391974335143015, -0.28234415509318983, 1.171997380422402, 0.99219850816143118, 0.57379812512229722, 0.3380671607475943, -0.18563757337316833, 0.96509518116988646, 0.37347847080985874, 1.1437665564868251, 0.031760000475600014, -0.39609364776911171, -0.3137780353445761, 0.10653229342718207, 1.1533366236132849, 0.07830300179968705, -0.6990446682585254, -0.15672629959865172, 0.045729567422830297, -0.013282520903323547, 1.0784172089962345, -0.60224591122005555, -0.2400816105444519, -0.7540284682233076, -0.020222000996376638, 1.274306070215687, -0.64533344210893129, 0.28042368385712529, 0.27580072457476978, 1.5182955503872977, -0.0047304621065292762, -0.50317420170928551, 0.4573945965199005, 0.39455994706458375, -0.56433374123496294, 1.1314731915221559, -0.43446474559881809, -0.19498853851294695, -0.26823912529088589, 1.1933101881100889, -0.37757356049842083, 0.68916993408614124, 0.049471507505717188, -0.051888797847267895, -0.55491370926206596, -0.1396850660198484, 1.6141546572189398, -0.0055381708458127621, 0.098319681243765333, -0.15540916458551837, -0.21662996811678728, 0.092025613120731709, 0.630962355507934, -0.14728528091664408, 0.17787408210037239, -0.58468269213537882, -0.22031950840882325, 0.75609915765504421, -0.23461341667769045, -0.39571496664518979, -0.51598080809333491, 1.2380507522892159, 1.4459289428610405, 0.12060024453366913, 0.40920632661226142, -0.9303154242590872, -0.70118706622320515, -0.10450127684683463, 0.36101489786664204, -0.15950890870660961, 0.067527402681617735, -0.30642858487211833, -0.38062071955896049, -0.31188870355713849, -0.037705414177403584, -0.42027003497146764, -0.046823129097793237, 0.99998061585563558, 1.1033610204229904, 1.3061552708331139, 0.16918144064965146, 0.6840128077277402, 1.7838351011139935, 1.3403219982972003, -0.96281895273115614, 0.24494968225326391, -0.4541011550949664, -0.12570939454504024, -0.26133180091949243, -0.10901423367565408, -0.72009215065780063, -0.18812438211247565, -0.54286349423206393, -0.48998684603379222, -0.020901762721460049, -0.58725062488901125, 0.18337142579335175, -0.31938968187335504, -0.10508624684921121, 0.40806259555367996, -0.10210272233508183, 0.83451590105445794, -0.089358813840398787, 0.37950120834313777, 1.1076970910349522, 1.4514782262478441, -0.28596454350198336, -0.13600619751457071, -0.14115052653258822, 1.1762533186650914, 1.1675170313500733, -0.7755896005317704, -0.33652300134096885, -0.30615908132550013, -0.35384562177758766, 0.4906866359852477, 1.2888046436537846, 0.22714668562534968, 0.99001479202435716, 0.98372098906741778, 1.3057858140845542, -0.5425811565315346, 0.58074230672441662, -0.43547959399369718, -0.20167695088650289, -0.27855649824271977, -0.5761249689659107, 1.1635996207278305, -0.21304147583840696, 1.3829569878598615, -0.16201234208885834, -0.07495858591021641, 0.38988994974452296, 0.011483235814389356, -0.096039270003873661, 0.00013086568565101597, -0.11205848655213851, -0.24774170544656543, 1.1009293997023732, -0.010792439679455618, 0.88026732647002415, -0.55108642341748393, -0.3198432416077025, -0.68815010227193263, 0.75357213705151038, -0.57901213200148116, -0.031242875570958221, 0.11788396263820924, 0.21580098845780249, 0.32538026460592356, -0.20839348397546345, 0.86949273617790768, 1.922948162161364, 0.14242833435174443, -0.30261822003789579, 0.035261899227137489, -0.084056784325899192, -0.83727956463870146, 1.1098062356342375, -0.041135405119677226, -0.066487208764673414, 1.3531085477124516, 1.4509295958665713, 0.038646013759908893, 0.47956261787879972, 0.12362686802005252, -0.37647958309497553, -0.32292427043883332, 0.075509095830659734, -0.38982732520184488, 1.8160821914902143, -0.28291207465273721, 1.1584213578465823, 0.055569124240822387, 0.80750356817219004, 0.67882302680027207, -0.31121443125235604, 1.4954598545652011, 0.48552575403397508, 0.13468771673517227, -0.41125782453773319, -0.47756262664202442, -0.56954240677341139, 0.00019456640526810398, 0.95519085573011953, -0.44633830685334908, 0.055784380286543811, 0.91551585039204109, -0.83703701244344564, 1.5814044581975437, -0.74306556151236536, -0.10695769934885441, -0.018623699239776415, -0.02780160406064093, -0.036483844953548214, 1.3938318185137064, -0.29587938479393505, -0.015558480329029012, -0.46054260710422285, 0.043115903892804813, -0.22142235489317841, 0.0033591095427602247, -0.22260616284184678, -0.35965643422292931, -0.60649069155215107, 0.83233789459324314, 0.90489660651785297, 1.8042742654497022, 0.026679680942828021, -0.23902378294326204, 1.3063725703653322, 1.1355468213179729, 0.0014488267115074227, -0.45458958817732126, 1.4739115085763661, -0.38750860615115434, -0.68270503368284996, -0.15204374501710843, 0.91578922129918672, 0.082381597510943733, -0.27306408204897054, -0.41495911992361623, 0.074591607631540435, -0.62686225034017551, -0.47476158330904283, -0.39673256225378001, 0.97560593742395074, -0.3024956015209091, 1.0082022852354917, -0.2929979209405853, -0.69950282259061414, 0.13824709372555671, 0.039136244416886318, 1.1637247487413358, -0.6077194014881917, -0.50419317208913006, 1.0177075278864076, -0.26168395528012639, -0.62683591832573249, -0.4538505347631383, 0.069034469862364722, 1.0221519300207067, -0.32087657198910136, 1.2549669416866347, -0.44837105221463791, -0.13584174606232069, -0.28493569250782269, -0.27993296583175448, 1.4974682359649605, 1.5747988762816947, 0.37076768194121906, 1.4164831179378123, 0.35511172526413598, -0.062207294137420599, -0.25568422369707472, 0.21603916681389801, 0.95223766869165338, -0.065980588918259003, -0.17058511015531841, -0.35327648063946454, 0.065113989007728315, -0.087196986534365861, -0.80412554510286716, 0.34387558096006504, 1.6819078855026488, 0.70128408250936425, -0.24875672194936516, -0.23086722918310104, -0.64168850380693132, -0.48905283717004971, -0.11072626646275409, -0.050250794504127769, -0.3403068815244899, 0.32572197033073924, 0.17862031273483087, -0.27225068758442483, 0.65985262180175119, -0.37018423022622871, 0.28260141946215428, -0.41213271627343567, 1.0771561809555448, -0.47454901979633218, 0.12159224346287222, 0.76039831142147274, -0.025607509682797236, -0.097148563715351188, -0.53137784316815728, 0.054254787739384525, -0.036581435570190945, -0.25463337719490964, -0.31121878309690698, -0.068383103632351672, 0.62037816416748459, -0.29706923459461704, 0.55203603740721308, 0.15563116578717845, -0.53592733648992397, -0.17771723963514552, 1.2678676223569971, -0.84339293144573602, -0.48006071939448547, -0.79404582608874086, 0.030865332523574984, -0.036117597499419743, -0.59836932713015634, 1.4475326220532618, -0.49426792436657596, 0.050473100140547955, 0.0030713540618398927, -0.38733330681536382, 0.027253664019934315, 1.0760510638822987, 0.39745366716115527, 1.3146235132250654, -0.238667472205147, -0.2359421793670306, -0.012722962898331788, -0.72803237999515791, 0.49048156162838979, -0.62107924936880432, -0.61191937104936867, -0.78096756393153532, -0.80629907405000256, 1.2726137297564486, 0.94918699285106589, 1.534489085888334, 1.2157207022428735, -0.46788665893862819, 0.8483145775895865, -0.38350038089509486, -0.085663042878314263, 1.449856912501287, 1.0022830213262763, -0.11379232140776098, 0.14977604404476366, 0.0063507369900386301, 0.60421713463242466, -0.37786143742211764, 0.12735901597475685, -0.53764393612641814, 0.0069083261877289429, -0.33424615133401958, 0.37432210184341047, -0.013619083894638159, 1.0553327504711527, -0.16003453054032826, -0.40482091364097939, -0.58443574369719942, -0.33435724300172887, 1.1910962081420942, 0.12688812422164369, 1.1013523325292371, 0.087631833615909593, 0.92714986809518429, -0.19857445907247562, -0.19082103696324199, 0.13436981460152878, -0.38140941858402178, -0.51794769340901592, -0.089797030019986346, -0.53706232118137831, 0.68752204348604373, -0.33202476670474307, 1.9523459044477935, -0.19313329212512514, -0.14295669309029349, -0.28732372208084633, -0.14793144392214697, -0.77768003220579385, 0.11538021978915458, -0.59993506764722615, 1.6236110721620114, 0.83369118253216912, -0.44254893454453309, 1.0280154013623979, -0.80675911817432344, -0.18037609690787712, -0.59297198855990607, -0.44440357786875861, 0.092471462583992831, -0.27498766011901449, -0.084518100750505754, -0.45211859377320951, 1.3612480217319647, 0.20373158687444687, 1.4820311135202582, 1.279083044575317, 1.2693392536457841, 0.18562015478510047, 0.032142624457422719, -0.12756149336744149, -0.12467294115386608, -0.068131947381332547, 0.086437439569013397, -0.20804809203304392, 0.16126408178766266, 0.068840669023966516, -0.27798827404473642, -0.45958272223081842, -0.038815286315619243, 0.15376632422083314, -0.52037945275640229, 0.021719939722146531, -0.17208354565576323, 0.71751135794757781, 0.8909697833300092, 0.064036561433787442, -0.47683031149948635, 1.4309895285057834, 0.59717835725946089, -0.23274612005701506, -0.40368426686756098, -0.17696533907424006, 1.1266816654928207, -0.54412707804651106, 1.3837361798753094, -0.46766578702981909, 0.62274233566296433, 1.2725691989171084, -0.16884968757230812, -0.40936891829399313, -0.27380909000897424, 1.5956996418554092, -0.35204418299452822, 0.0095511046200727756, -0.55329626880918759, -0.30046194680310578, -0.99005010892083389, 1.174978093086857, -0.73775184018312923, -0.25577447255806079, -0.21585158150781719, -0.24653115587321947, 1.1680694149443411, -0.38528477239261727, -0.032147735444822273, -0.83555447408369199, -0.4126525804456746, -0.050517435741114683, -0.014638175469989412, 1.6213210599407302, -0.22047846656735676, 0.10269692272836745, -0.074658715223975436, -0.22574651028663967, 0.023965667976282093, -0.57209188906987307, -0.09905432723500196, 0.7960948858358764, 0.23036757536079888, -0.6542558010966002, 0.22033743145794177, -0.65293706277406283, 0.229611541596804, -0.29968554358275157, -0.10769499442218369, 1.5901485597820102, 0.21605125792078445, 0.28462249159962533, -0.55686947357237138, -0.44090492992725516, -0.11492499954203138, -0.1900292976025576, 0.078377589257198899, -0.078396717809311345, 1.3731214737048481, 1.2733368782680559, 0.019124054207397344, 0.038882221957475593, -0.5266765986647578, -0.25721339776808433, -0.49515383870754942, -1.0338545720059691, -0.28158305831587527, -0.31799949446944104, 1.1236562469878337, -0.51052820560232659, 0.92040378249096833, -0.0088842307885581978, -0.585155559985722, 0.21765476066211795, -0.52602598118467569, 0.06556776192588698, 1.5004858865517083, 0.2097932059118372, -0.44696539995964163, -0.81865880237618205, -0.48866269501525567, -0.85419101308806589, 1.4453453988011808, -0.098006960155884407, 0.33296900516834138, 1.145284830560604, -0.28194615324466704, -0.39732791665117267, -0.10628462860803815, 1.0997006118013286, -0.19696542443410467, 0.075416868482104674, -0.61314599235413603, 0.69629659996705939, -0.031102841500568648, 0.15824974128407338, -0.61503407673249577, -0.13084050866413324, 0.26131774214284342, -0.09192249129219926, 0.04518164818169132, 0.15631836675696287, -0.33360331072000693, -0.19952084066653478, -0.19912390433814373, -0.70383984094283081, -0.37405369159285334, 0.032919954118161066, -0.49453036934731548, 0.69881392356589767, -0.84882350974055787, 1.3022313325313732, -0.49873489992777525, -0.12699737320090365, -0.53955786276408824, 1.2865910031942593, -0.57498459091139953, -0.34291748767142533, 0.50875309707954119, 1.1607606293682762, 0.016434577713838328, 1.5806437258103458, 0.46344371625418079, 1.9059966207177406, 1.117354138233468, 0.13259730210317389, 0.41888793108610589, -0.049737528802155984, -0.37663574834820202, -0.6269238463705249, -0.088411767856484524, -0.4267364585816798, -0.36277496695189093, -0.47619425672224813, -0.60392461610883696, 0.013391858497287218, 0.10381101717129965, -0.33445113046378938, -0.34774288524595942, 1.0075679141153702, -0.042591919281010693, 1.1436320922775485, -0.25923063563400889, -0.37664256350156833, -0.43221333628751818, 1.4139959264768915, 0.51334453393405444, -0.063419452769400933, -0.20763079371808699, -0.388511704744116, -0.39541746820705276, -0.22524551366196674, -0.29136566838595962, 0.9731117217576789, -0.58983068212391299, 0.83752716519556714, -0.54832323588333598, 0.01435405865951847, 0.79907860080820436, 1.3233667522323551, 1.3249323343945434, -0.39306877003825191, 1.0056753547885435, 0.54095746067279382, 0.426178462099085, -0.45366356137794306, -0.21400290577151343, -0.40443825611193474, -0.49603410510464097, -0.34992851505863659, -0.26756911672256789, -0.69627810056760775, -0.2810370021182057, 0.39373011421786203, 1.5471911086194468, -0.48534700357385036, -0.033327957981984402, 0.055576318369221667, 0.61643721233352089, -0.7296467235885391, -0.098495217557483972, 1.6881411018368122, 0.031753350447072365, 0.51160434958610645, 0.024444618958756215, -0.023022360778610029, -0.37903849293086728, -0.5155342504201087, 1.4354395438416372, 1.6419622961046054, -0.79679250549332692, -0.096083275018058023, -0.68589166262501755, 0.29766664375137325, -0.68246000326274625, 1.2150887033379087, -0.29561734495314751, 1.0096700528832872, -0.58687609277373887, -0.15965483329070806, 0.98374334180282819, 0.92947850520196029, -0.32362935388750397, -0.27330707295443835, 1.2274834385121012, 0.59086260132501578, -0.80661990800708905, -0.21096340862595492, -0.2283632364301538, -0.58197250389864474, -0.23997405076919856, -0.096366456057952771, 1.5446483253093497, -0.054868680304493704, -0.014594780899233495, 1.1580763840091666, -0.057043365488948283, -0.2507583405194509, 0.19105324418368747, -0.54880732237184127, 0.099880090041974415, -0.32271561612913252, -0.69364587319780113, 1.4637055121924274, -0.52590563620200015, -1.1035539338089504, 0.8421996929179022, 0.077794691151240922, -0.24391563297271179, -0.21723798778051268, -0.30939893462770851, -0.086213296716086477, 0.06414718317909901, -0.75929397973828938, -0.066416691697494673, -0.14827808198597059, -0.32353711579557792, 0.12818327560672188, -0.34187557966175913, -0.86839272581632365, -0.45405300703978946, -0.35567909364100747, 0.31956395594861731, -0.20600442450234832, 0.14681603412370897, -0.1530570434574241, -0.33935493293189306, -0.24283377457676739, -0.17324675960648761, 1.2723746999252445, -0.75343471071846957, 1.2017319358033776, -0.37232664118751424, 0.78332698250312849, 0.075406634297754654, 1.2518241123295175, -0.62397769497588107, -0.73594921134738622, -0.48003550900392317, 1.3189979435566588, 0.38883773947685968, 0.8728979119776229, -0.60875059292414546, -0.66179992256751341, 1.1585797048062441, -0.127227061420286, -0.38546631711804291, 1.1004838557659031, -0.69421315012016582, -0.28958060974913652, -0.64333499802913097, -0.31151644501195952, 1.2018951427399511, 0.032281218369642754, -0.20679494088250136, -0.22623879875673372, -0.50377509262692033, -0.05922080748283582, 0.24553862868622389, -0.29793073563822126, -0.43747465697294075, 0.20774621543287564, 0.20540358125732472, -0.80354221927015723, -0.52612316266413206, 0.088399484228591313, -0.33451176232498225, -0.23823699810896487, -0.55539174838421201, 0.61741821575282629, -0.3310896628434517, -0.23354617974488648, 0.70985819783347393, 1.1224100721725598, -0.38312619979822354, -0.22584558605194133, -0.27597808176950944, -0.4005949511969622, 0.012330802347886471, -0.28705558323188313, -0.41133096922525342, 1.5385502834001223, 0.00064622795470992145, 0.22206641949592532, 0.69216211121726778, -0.15864146447048116, -0.18870625193439197, -0.68684095971908987, -0.037270047742964843, -0.85258496152276908, 0.64690626388572992, -0.53931175996415692, -0.47127898818828706, -0.054131680484849509, -0.24327856694626804, 1.2353844164189021, 0.51295379988519196, -0.30839039569957288, 0.085292863422788023, -0.011851885256110516, 1.3952462253916491, -0.13021481799154239, -0.089251450003616367, 1.2013202040106423, 1.0863132317138149, 0.11757794698789609, 0.93438535558401603, -0.077181753065296232, -0.38368593885909497, -0.27773906837197487, -0.096456722688838592, -0.076647972234816897, 0.28806513428517533, 0.070539630007793119, 1.5359592953197971, 0.74850406539322645, 1.3648896340101258, -0.40425568101676301, 0.035351579562941382, -0.27213688807777137, 1.4332243480215705, -0.46174100303630816, -0.27644184605126654, -0.62135564202875937, -0.5233706478329323, 0.049569543802429922, 0.74373417160953803, 0.23325162885568507, -0.23296017192697727, 1.3229431726290097, 0.11400702279971725, -0.72345472508660613, -0.27887741772362568, -0.38967311941165, -0.7313926569882947, 0.88071757583719079, 0.10738955844441767, 1.0193908622621817, -0.77456408366832386, 0.35509853992477303, -0.67788530153172299, 1.6538339232383477, -0.14981500722330465, -0.013868256170097126, 0.86179256251663916, -0.68035907746449453, -0.41885437577724621, -0.32674667146776853, -0.011429055809038691, -0.054497003517779474, -0.29539865969754486, -0.48821315460583198, -0.050410099166748712, 0.043625550973431806, 0.73668920847493424, 1.2953844180931833, -0.089981988646161845, 0.4237484227327335, 0.1519309777819991, -0.01342800776885042, 1.4366789008047873, -0.59921733194315663, 0.82588175516603757, 0.089898955379562551, 0.11093471253574842, -0.68398753380967703, 0.81135143204608129, 0.99543922067631285, -0.66234938618862982, -0.34395031776822071, -0.38622630094276911, -0.0071677706971049748, -0.62423076997949667, 0.97051017856134958, -0.30845470107195061, 1.1378772007121878, 0.51816586534132847, -0.16110730261937584, -0.0024412000264404099, 0.11593347414711513, 1.2280860857353197, -1.3123173476237018, 1.2692952665840678, -0.54966206865854672, 0.7159463213791476, -0.589917044598117, -0.44776584211041026, 0.9685606583306271, 0.9171851603452631, -0.2973647343371309, -0.16403089550238742, 0.39980976498390886, 1.1606698464418788, 0.15435607905393717, -0.8661449042120507, -0.39172971236321802, -0.83970396136169589, 0.21796533463757031, -0.025838980106683396, -0.25822275977951081, -0.01564821896090976, -0.45224670900694652, -0.1626554340773087, 1.8437477508170215, 0.27451820869417237, -0.19563134936518634, 0.0096652880924681106, -0.73081043268996748, -0.24263858588324455, 0.71599880084805534, 0.070244054384646037, -0.52833506865710433, -0.68829426871684829, -0.20939864836189501, -0.43327382886377491, 0.001697952655578705, -0.30966731668971609, -0.61344558537310356, -0.28770949345013325, -0.81677997496801291, -0.277783047137307, 1.7181685703126244, 0.060206484589723587, 1.5115899398301831, 1.1635338528069337, -0.61730578261788605, -0.19555864230941045, -0.42906297585797715, 1.4629939990150911, -0.38176678111997459, -0.0902042591458036, 1.0344360385524236, 2.0564622248503301, -0.88109092280543999, 0.011815295807092813, -0.30098376382421588, -0.21178601137382411, 0.2661574483903445, 1.3072219054620138, -0.45420946646066318, -0.061593181784592221, -0.37350315220774244, 0.065725996604681036, -0.24248728857563667, -0.2298099329168779, -0.15782239766048023, -0.86867996758154931, 1.1887989720175249, -0.35292538622455077, 1.5233153697567356, 1.1252475325155473, -0.87214208978898955, -0.40497352929305308, -0.073769483441120332, 0.99793909710094775, 0.057920248609151515, -0.20989400937194191, -0.23060759130078307, 1.4612259301802952, 0.10951290940554956, -0.41105811176199614, 0.30750288895285588, -0.26572551949064183, -0.55353895500443695, 1.5138562206478263, -0.058401534770543911, -0.12082142333529855, 1.0624295124219172, 1.4188190345235387, 1.8999146815132146, -0.47256562805605362, -0.48074740621044409, -0.36502215480727362, -0.39606369018917842, 0.19515496540616079, 0.097810934272410638, -0.56357553573593022, 1.0550730219381637, 1.2791899193451615, 0.8609696312353079, 1.3887364625476004, -0.35993304615089794, -0.45849457555746109, 0.042749068002721505, -0.11290867031457524, 0.27218288304211091, -0.2225954145616546, -0.13457184070242822, 0.08597173070941741, -0.34727570514569983, 1.5144708606953059, 1.1389483144503667, 0.41645581221403594, -0.42523946116085048, 0.88157149694117765, -0.011411277425508157, 0.63913086427809684, 1.1577819713126352, 0.61208538684295422, 1.5263391415579379, 0.61340326172898829, 1.273778124596268, -0.17744741196110891, -0.061014947793356922, -0.72723607198482898, 0.37300026391578883, 1.0317779517144694, -0.50930740703745103, 0.022573538261515202, -0.35631608377850688, 0.067389050861414723, -0.18582962487452573, -0.66841586055374491, 1.3917485907052709, 1.1108293300711742, 0.14893552910283325, -0.023926557122031018, -0.21541077931240307, -0.23609360107001134, -0.10835870313003546, 0.59411525384762398, 0.94335554948162248, -0.27751453854961727, -1.1182629787458911, 0.015152731922147611, -0.28950768405552346, 0.11377062100313931, 0.24719176753026539, -0.23394684455856304, 0.094237453247170144, 0.96074824838536776, -0.14604340620029926, -0.65985364040106442, -0.1721999889283701, 1.0192649026832068, -0.3611286699225279, -0.57117547661801671, 0.55967545171400668, -0.099773486571447162, -0.6102313300665454, 0.092533486035233953, -0.050887509814598525, -0.26787114600013101, 0.2671384190243003, -0.44237407795045242, 0.2553782017432758, -0.51458375340129814, 0.41409481397813785, -0.47136000031531722, -0.69067218789288076, 0.23881891069595973, 0.26196379971428557, -0.17102079649944821, -1.008369702593219, 1.1023572915692161, -0.1376625703303152, 0.043494508778560037, 0.15814684987661626, -0.070765612705119274, -0.024093579398196369, 1.1359955078816608, -0.519982492560502, 0.018121909429201999, 0.70999777833098965, 0.028514895563885603, -0.17544941870213612, 0.053579818919571198, -0.21484269228217887, -0.69066765593774271, -0.43837892163294645, 1.5232730556851737, -0.4152039698738772, -0.24204628606145884, -0.058367303298399398, 0.23469112760955757, -0.78725323416695203, 1.295344229551525, -0.54996676351110207, 0.95298788339710916, -0.16012538579545005, -0.58789917899163024, 1.0849811058361702, 0.79213806699001799, 1.5544696215355698, 1.1981261090133202, 1.409453386403779, -0.37826464853340019, 0.22968944778075706, -0.56825552215425579, -0.38334900879368788, -0.27302660094247805, -0.21603594368787105, 0.17855010555357109, 0.33356938822114224, -0.034583401702194941, 0.094252432683898102, -0.11272203944244469, 1.5549970487754567, 0.46265635256414872, -0.67566576495000019, -0.085792793422777722, 1.7871475266076082, -0.26483022969943387, -0.82181706907473218, -0.55350034966611994, 1.1974785130471179, 1.301155973991786, -0.11216657609132397, 0.76893802000459921, -0.37455072379915638, -0.25627025422811034, -0.23537472751874325, 1.3643732058371694, -0.38308181615569331, -0.39023392162663167, 0.14474925794909943, -0.92978090893700083, 0.90433842250664687, 0.15505935725962317, -0.67440477166579615, 0.1854903344335404, -0.14950064206980013, -0.45717404786988602, -0.49104662496807727, -0.54852574003814847, 0.74876930042089618, -0.2366507421756269, -0.013543644736342628, -0.31956820173259459, -0.81199274603834015, -0.42045041641529424, -0.90050635159789794, -0.49409815973582794, 0.18368017886092169, 0.13266183047070634, 0.054093340531784745, 1.5629761281289301, 1.0393418667836039, -0.026709019509336657, 0.83922767779564844, 1.2051273535283618, -0.20371646220008641, 0.29219263497903286, 0.27908007510255495, 0.25304751688113947, 0.56464980716587609, -0.87659253194297038, 0.47619984499076795, 0.40802102146726199, -0.48336699598233768, 0.30745929452704274, -0.5936024152411401, 1.5024702131771992, -0.41753229878756626, -0.24498921569565202, 0.15410401358844644, 0.075049725509808596, -0.54951573931766506, -0.14395669302970845, 0.60665453996530294, 1.2171590196627542, 1.2716147926939727, -0.031495081156300932, -0.52318506620704563, 0.48336499540369837, 0.65121472519186607, -0.5643803199856624, 0.23134356691548408, 1.0407208571463156, 0.023091803832147206, -0.22634205839173097, 0.041539904545610989, -0.058370252920740773, 1.5590609866791634, -0.25883727664848405, 0.17053822251434964, 1.2958676587706361, -0.32403489061265384, 1.3802075748631355, -0.42990305639386805, -0.98621692300327346, 1.1723033832198639, -0.46761195716180542, 0.0567947184319018, -0.375693825827149, -0.061479250376987343, 0.11392156480600943, -0.37061061343208035, -0.10906809534877918, -0.14752278790739495, 0.0062483012248667757, -0.11691843662909673, -0.19548783645745538, -0.3561701710536529, -0.43631596401141409, 0.71026815161652135, -0.92671016894995117, -0.26985366422294088, -0.34448439828752936, -0.66950802184069214, 1.3278934292565445, 1.5726341724870168, -0.40470085062478633, -0.68550194359652128, -0.19932332331628655, 0.6430379242524874, -0.070640051783538271, 0.02944845984633937, -0.44950087466577843, -0.57251464769094329, -0.29728375641752613, -0.38020365087724228, -0.38614187260540078, 0.036284989763950559, 0.073565999678306959, 1.2317196480992501, 0.45013011138906855, -0.20316701129860132, 1.1859753628111902, -0.71283958457564656, 0.98068199550904323, 0.12448783087606696, -0.042259048303264136, 0.61508357784416579, -0.82337977062939183, 1.310962697645097, 1.6783285874543201, -0.20412618320613513, 1.0702663760318334, -0.15473503447651887, -0.089191188494342197, -0.49370717597437946, 0.96318440024083851, 0.068927173727740987, -0.058946276335350184, -0.28873027648474175, -0.19298680894283624, -0.33888816210242778, 0.69511668103495783, 1.5150857435304121, -0.59588018350336669, 0.46754294477197278, -0.14428698286856317, -0.098106488657001195, 0.51789740224765046, -0.43457462889627907, -0.47356213777468242, -0.022912312184694555, -0.089613988381571419, 0.31730974930005024, 0.19267200957448077, -0.48558595497853962, 0.84415549331692485, -0.11350947494916147, 0.062773592442448123, 0.14042861729964351, -0.28414782553802215, 1.431601577504656, -0.32890044392420581, 0.5790195880201261, 0.4032224480343029, 0.88125483753477074, -0.49090590000250856, -0.40525049079105974, -0.8869157770914784, 0.10472164981149781, 0.42461686827758405, 0.20991931955429544, -0.015086055917663654, 0.49173614745937716, -0.47971188873394804, 1.2669287807423513, 0.12002844976188709, -0.39462920712123056, 0.66693522479016698, -0.24474143098877374, -0.61606627532351754, -0.42750549170693736, -0.44073383190192211, -0.48860559609360865, 0.78453759284238789, -0.28642528886033219, -0.17103846923371482, 0.37611905861144795, 0.26341635617225911, 1.4252449887401093, 1.3626680318426421, -0.30999336739745365, 1.0005341905451528, 0.10058978527507731, -0.047465971910712002, -0.20696278896824175, -0.37752767246531349], + "t": [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0], + "t_one": [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + "x_ties": [-0.78000000000000003, 0.029999999999999999, -0.65000000000000002, -0.60999999999999999, -0.28000000000000003, -0.46999999999999997, -0.60999999999999999, -0.33000000000000002, -0.040000000000000001, -0.88, -0.19, -0.28000000000000003, -0.94999999999999996, -0.51000000000000001, -0.65000000000000002, -0.60999999999999999, -0.92000000000000004, -0.40000000000000002, -0.25, -0.46000000000000002, -0.40000000000000002, -0.11, -0.20999999999999999, -0.83999999999999997, 0.02, -0.25, -0.57999999999999996, -0.23000000000000001, -0.46000000000000002, -0.44, -0.66000000000000003, -0.79000000000000004, -0.33000000000000002, -0.78000000000000003, -0.92000000000000004, 0.12, -0.19, -0.93999999999999995, -0.46000000000000002, 0.38, -0.34999999999999998, -0.58999999999999997, -0.17000000000000001, -0.28999999999999998, -0.089999999999999997, -0.40000000000000002, -0.54000000000000004, -0.10000000000000001, -0, -0.01, -0.20999999999999999, -0.81999999999999995, -0.47999999999999998, -0.059999999999999998, -0.52000000000000002, -0.34000000000000002, 0.28000000000000003, -0.32000000000000001, -0.11, -0.60999999999999999, -0.26000000000000001, 0.11, 0.029999999999999999, -0.23999999999999999, -0.65000000000000002, -0.73999999999999999, -0.79000000000000004, 0.46000000000000002, 0.34000000000000002, -0.28999999999999998, -0.28999999999999998, -0.41999999999999998, 0.10000000000000001, -0.89000000000000001, -0.66000000000000003, -0.83999999999999997, -0.37, -0.60999999999999999, -0.059999999999999998, -0.19, -0.32000000000000001, -0.72999999999999998, -0.22, -0.37, -0.23999999999999999, -0.37, 0.33000000000000002, -0.40000000000000002, -0.60999999999999999, -0.72999999999999998, -0.26000000000000001, -0.40999999999999998, -0.63, -0.93000000000000005, -0.40999999999999998, -0.20000000000000001, -0.84999999999999998, -0.33000000000000002, -0.31, -0.11, -0.56000000000000005, -0.83999999999999997, -0.56999999999999995, 0.059999999999999998, -0.26000000000000001, -0.41999999999999998, -0.45000000000000001, -0.17000000000000001, 0.17000000000000001, -0.62, 0.35999999999999999, -0.73999999999999999, -0, -0.20999999999999999, 0.029999999999999999, -0.11, -0.42999999999999999, -0.19, -0.68000000000000005, 0.23999999999999999, 0.34999999999999998, -0.40000000000000002, -0.11, 0.10000000000000001, -0.31, -0.01, -0.71999999999999997, -0.16, -0.58999999999999997, -0.34999999999999998, 0.23000000000000001, -0.68999999999999995, -0.64000000000000001, -0.66000000000000003, -0.32000000000000001, -0.72999999999999998, -0.67000000000000004, 0.040000000000000001, -0.12, 0.37, -0.29999999999999999, -0, 0.28999999999999998, -0.19, -0.82999999999999996, -0.34999999999999998, -0.25, -0.75, 0.32000000000000001, 0.17999999999999999, -0.28000000000000003, -0.39000000000000001, -0.55000000000000004, -0.35999999999999999, -0.63, -0.45000000000000001, -0.42999999999999999, 0.070000000000000007, -0.40000000000000002, -0.19, -0.72999999999999998, -0.54000000000000004, -0.65000000000000002, -0.89000000000000001, -0.33000000000000002, -0.31, -0.12, -0.83999999999999997, -0.029999999999999999, -0.51000000000000001, -0.38, 0.45000000000000001, -0.68000000000000005, -0.10000000000000001, -0.40000000000000002, 0.029999999999999999, -0.23000000000000001, -0.69999999999999996, -0.81999999999999995, -0.59999999999999998, -0.56000000000000005, -0.40000000000000002, -0.22, -0.63, -0.14000000000000001, 0.070000000000000007, 0.050000000000000003, -0.34000000000000002, -0.46999999999999997, -0.71999999999999997, -0.81000000000000005, -0.62, -0.39000000000000001, -0.37, 0.14999999999999999, -0.050000000000000003, 0.34999999999999998, -0.23000000000000001, -0.38, -0.66000000000000003, -0.12, -0.42999999999999999, -0.28999999999999998, -0.050000000000000003, -0.37, -0.34999999999999998, -0.40999999999999998, -0.42999999999999999, -0.42999999999999999, -0.81999999999999995, -0.44, -0.65000000000000002, -0.57999999999999996, -0.72999999999999998, -0.22, 0.63, -0.79000000000000004, -0.28000000000000003, -0.33000000000000002, -0.62, -0.60999999999999999, -0.93999999999999995, -0.040000000000000001, -0.14000000000000001, 0.02, -0.55000000000000004, -0.69999999999999996, 0.16, -0.81000000000000005, -0.53000000000000003, -0.68000000000000005, -0.93999999999999995, -0.63, -0.23000000000000001, -0.95999999999999996, -0.16, -0.17999999999999999, -0.25, -0.48999999999999999, 0.25, -0.46999999999999997, -0.69999999999999996, -0.34999999999999998, -0.37, -0.69999999999999996, -0.23000000000000001, -0.77000000000000002, 0.029999999999999999, 0.32000000000000001, -0.17000000000000001, -0.54000000000000004, -0.93000000000000005, -0.53000000000000003, -0.080000000000000002, 0.089999999999999997, -0.11, -0.20000000000000001, 0.38, -0.38, -0.11, -0.42999999999999999, -0.16, -0.089999999999999997, -0.92000000000000004, -0.44, -0.65000000000000002, -0.82999999999999996, -0.35999999999999999, -0.42999999999999999, -0.82999999999999996, -0.85999999999999999, -0.62, 0.39000000000000001, -0.46000000000000002, -0.70999999999999996, 0.050000000000000003, -0.40000000000000002, -0.81999999999999995, -0.62, -0.31, -0.77000000000000002, -0.48999999999999999, -0.60999999999999999, -0.01, -0.54000000000000004, -0.81000000000000005, -0.41999999999999998, 0.34000000000000002, -0.53000000000000003, -0.31, -0.54000000000000004, -0.25, -0.64000000000000001, -0.14000000000000001, 0.089999999999999997, 0.14000000000000001, -0.60999999999999999, -0.69999999999999996, -0.5, -0.39000000000000001, -0.62, -0.57999999999999996, -0.72999999999999998, -0.75, -0.40999999999999998, 0.12, -0.68999999999999995, -0.44, -0.20999999999999999, -0.41999999999999998, -0.32000000000000001, -0.02, 0.33000000000000002, -0.22, -0.57999999999999996, -0.33000000000000002, -0.46999999999999997, -0.25, -0.16, -0.17999999999999999, -0.93000000000000005, -0.25, -0.23000000000000001, 0.12, -0.46000000000000002, -0.23000000000000001, 0.17000000000000001, -0.41999999999999998, -0.45000000000000001, -0.53000000000000003, -0.40000000000000002, -0.46000000000000002, -0.26000000000000001, -0.40999999999999998, -0.059999999999999998, -0.10000000000000001, -0.52000000000000002, 0.059999999999999998, -0.68999999999999995, -0.39000000000000001, -0.78000000000000003, 0.22, 0.42999999999999999, -0.54000000000000004, -0.050000000000000003, -0.64000000000000001, -0.95999999999999996, -0.51000000000000001, -0.20999999999999999, -0.98999999999999999, -0.10000000000000001, -0.82999999999999996, -0.66000000000000003, -0.55000000000000004, -0.20000000000000001, -0.38, -0.81000000000000005, 0.02, 0.050000000000000003, -0.31, -0.38, 0.23999999999999999, -0.01, 0.17000000000000001, -0.95999999999999996, -0.28999999999999998, -0.80000000000000004, -0.69999999999999996, -0.47999999999999998, -0.42999999999999999, -0.26000000000000001, -0.80000000000000004, -0.67000000000000004, -0.17999999999999999, -0.51000000000000001, -0.46000000000000002, -0.73999999999999999, -0.02, -0.01, -0.67000000000000004, -0.81000000000000005, -0.62, -0.47999999999999998, -0.34999999999999998, 0.44, -0.60999999999999999, -0.37, -0.089999999999999997, -0.17999999999999999, -0.58999999999999997, -0.19, -0.57999999999999996, -0.57999999999999996, -0.70999999999999996, -0.41999999999999998, -0.080000000000000002, 0.14000000000000001, -0.12, 0.059999999999999998, -0.79000000000000004, -0.29999999999999999, -0.63, -0.71999999999999997, -0.77000000000000002, -0.95999999999999996, -0.90000000000000002, -0.16, 0.19, -0.40999999999999998, 0.13, 0.059999999999999998, -0.20999999999999999, 0.28999999999999998, -0.13, -0.27000000000000002, -0.33000000000000002, -0.41999999999999998, -0.35999999999999999, -0.20999999999999999, -0.31, -0.62, -0.71999999999999997, -0.57999999999999996, -0.52000000000000002, -0.87, -0.59999999999999998, -0.10000000000000001, -0.20999999999999999, -0.63, -0.27000000000000002, -0.79000000000000004, 0.059999999999999998, 0.26000000000000001, -0.14000000000000001, -0.42999999999999999, -0.37, -0.75, -0.28999999999999998, 0.029999999999999999, -0.66000000000000003, -0.11, -0.40000000000000002, 0.20000000000000001, -0.56999999999999995, -0.68999999999999995, -0.48999999999999999, -0.53000000000000003, -0.68000000000000005, -0.44, -0.32000000000000001, 0.62, -0.76000000000000001, 0.5, -0.14999999999999999, -0.42999999999999999, 0.070000000000000007, -0.20000000000000001, 0.40000000000000002, 0, -0.44, -0.66000000000000003, -0.80000000000000004, -0.84999999999999998, -0.71999999999999997, -0.64000000000000001, -0.5, -0.91000000000000003, -0.31, -0.63, 0.40000000000000002, -0.91000000000000003, -0.70999999999999996, -0.53000000000000003, -0.25, -0.02, 0.28000000000000003, -0.56000000000000005, -0.46999999999999997, -0.56999999999999995, -0.38, -0.12, -0.28000000000000003, -0.46999999999999997, -0.81000000000000005, -0.82999999999999996, 0.050000000000000003, 0.28000000000000003, 0.080000000000000002, -0.55000000000000004, -0.55000000000000004, -0.76000000000000001, -0.44, -0.47999999999999998, -0.46000000000000002, -0.29999999999999999, -0.40000000000000002, -0.77000000000000002, -0.52000000000000002, -0.42999999999999999, -0.029999999999999999, -0.34999999999999998, -0.81000000000000005, 0.01, -0.28000000000000003, -0.029999999999999999, -0.73999999999999999, -0.39000000000000001, -0.70999999999999996, -0.40000000000000002, -0.70999999999999996, -0.69999999999999996, -0.29999999999999999, -0.27000000000000002, 0.02, -0.62, -0.37, -0.46999999999999997, -0.60999999999999999, -0.81999999999999995, -0.29999999999999999, -0.37, -0.66000000000000003, -0.56999999999999995, -0.12, -0.65000000000000002, -0.11, 0.20000000000000001, -0.38, 0.66000000000000003, 0.17000000000000001, -0.38, 0.33000000000000002, -0.16, -0.52000000000000002, -0.68000000000000005, -0.01, -0.93000000000000005, 0.080000000000000002, -0.28999999999999998, -0.59999999999999998, -0.059999999999999998, -0.58999999999999997, -0.56000000000000005, -0.10000000000000001, 0.01, -0.34000000000000002, -0.93000000000000005, -0.27000000000000002, -0.93999999999999995, -0.23000000000000001, -0.14000000000000001, -0.14000000000000001, -0.56000000000000005, 0.40999999999999998, -0.19, -0.22, -0.57999999999999996, -0.029999999999999999, -0.34000000000000002, -0.69999999999999996, -0.56999999999999995, -0.16, -0.01, 0.050000000000000003, -0.83999999999999997, -0.28999999999999998, -0.28999999999999998, -0.57999999999999996, -0.70999999999999996, -0.58999999999999997, -0.58999999999999997, -0.62, -0.27000000000000002, -0.62, -0.53000000000000003, -0.28000000000000003, -0.67000000000000004, 0.48999999999999999, 0.27000000000000002, -0.46999999999999997, -0.60999999999999999, -0.80000000000000004, -0.70999999999999996, -0.20000000000000001, -0.73999999999999999, -0.059999999999999998, -0.94999999999999996, 0.31, -0.45000000000000001, -0.25, -0.41999999999999998, -0.11, -0.41999999999999998, 0.22, -0.56000000000000005, -0.55000000000000004, 0.19, -0.42999999999999999, -0.84999999999999998, -0.85999999999999999, -0.69999999999999996, -0.56000000000000005, -0.96999999999999997, 0.080000000000000002, -0.050000000000000003, 0.11, 0.16, -0.01, 0.5, -0.72999999999999998, -0.11, 0, -0.77000000000000002, -0.46999999999999997, 0.34000000000000002, -0.81000000000000005, -0.67000000000000004, -0.62, -0.84999999999999998, -0.13, -0.080000000000000002, -0.23999999999999999, 0.080000000000000002, -0.26000000000000001, 0.11, -0.16, -0.31, -0.90000000000000002, -0.23999999999999999, -0.25, -0.45000000000000001, -0.17000000000000001, -0.64000000000000001, -0.72999999999999998, -0.52000000000000002, -0.32000000000000001, 0.25, -0.23999999999999999, -0.47999999999999998, -0.040000000000000001, -0.34000000000000002, -0.20999999999999999, -0.93999999999999995, 0.42999999999999999, -0.87, -0.52000000000000002, -0.35999999999999999, -0.14999999999999999, -0.85999999999999999, -0.64000000000000001, -0.48999999999999999, 0.10000000000000001, 0, -0.20000000000000001, 0.01, -0.70999999999999996, -0.56999999999999995, -0.52000000000000002, -0.19, -0.31, -0.63, -0.87, -0.13, 0.089999999999999997, -0.65000000000000002, 0.16, -0.050000000000000003, 0.28999999999999998, -0.17999999999999999, -0.67000000000000004, -0.77000000000000002, -0.12, -0.71999999999999997, 0.75, -0.81000000000000005, -0.63, -0.14999999999999999, -0.32000000000000001, -0.58999999999999997, -0.089999999999999997, 0.14999999999999999, -0.26000000000000001, -0.14999999999999999, -0.40999999999999998, -0.64000000000000001, -0.46000000000000002, -0.42999999999999999, -0.40999999999999998, 0.14000000000000001, 0.13, -0.34999999999999998, -0.68999999999999995, 0.14000000000000001, 0.17999999999999999, -0.28999999999999998, 0.31, -0.56000000000000005, -0.58999999999999997, -0.52000000000000002, -0.029999999999999999, -0.11, -0.28000000000000003, -0.42999999999999999, -0.41999999999999998, -0.32000000000000001, -0.57999999999999996, -0.080000000000000002, -0.81999999999999995, 0.27000000000000002, -0.46999999999999997, 0.13, -0.17999999999999999, -0.77000000000000002, -0.01, -0.14999999999999999, -0.46000000000000002, -0.28999999999999998, -0.59999999999999998, -0.22, -0.37, 0.26000000000000001, 0.02, -0.37, -0.69999999999999996, -0.72999999999999998, -0.029999999999999999, -0.56999999999999995, -0.20000000000000001, 0.17999999999999999, -0.93000000000000005, -0.57999999999999996, -0.29999999999999999, -0.53000000000000003, 0.080000000000000002, -0.75, -0.52000000000000002, 0.01, -0.25, -0.14000000000000001, -0.82999999999999996, -0.91000000000000003, -0.26000000000000001, -0.60999999999999999, -0.68000000000000005, 0.02, 0.44, -0.40000000000000002, -0.040000000000000001, -0.84999999999999998, -0.79000000000000004, -0.68999999999999995, -0.47999999999999998, -0.72999999999999998, -0.070000000000000007, -0, -0.59999999999999998, -0.050000000000000003, -0.66000000000000003, -0.23000000000000001, -0.17999999999999999, 0.17999999999999999, -0.70999999999999996, -0.78000000000000003, 0.40999999999999998, -0.17000000000000001, -0.23999999999999999, -0.65000000000000002, -0.75, -0.28999999999999998, 0.02, -0.19, -0.23000000000000001, 0.28000000000000003, -0.47999999999999998, -0.82999999999999996, -0.23000000000000001, -0.35999999999999999, -0.13, 0.17000000000000001, -0.81999999999999995, -0.54000000000000004, -0.28999999999999998, -0.28999999999999998, -0.32000000000000001, -0.29999999999999999, -0.02, -0.14000000000000001, -0.33000000000000002, -0.48999999999999999, -0.56999999999999995, -0.35999999999999999, -0.48999999999999999, -0.52000000000000002, -0.62, -0.37, -0.78000000000000003, -0, -0.25, 0.44, -0.34000000000000002, -0.93000000000000005, -0.63, -0.27000000000000002, -0.59999999999999998, -0.68000000000000005, -0.70999999999999996, 0.23999999999999999, -0.68999999999999995, 0.23999999999999999, -0.16, 0.5, 0.22, -0.68000000000000005, -0.47999999999999998, -0.80000000000000004, -0.11, -0.75, -0.17000000000000001, -0.23999999999999999, -0.48999999999999999, -0.66000000000000003, -0.62, -0.40999999999999998, -0.41999999999999998, -0.16, -0.40000000000000002, 0.14999999999999999, -0.42999999999999999, 0.02, -0.5, -0.42999999999999999, 0.029999999999999999, 0.17000000000000001, -0.53000000000000003, -0.040000000000000001, -0.28000000000000003, -0.39000000000000001, -0.32000000000000001, -0.14000000000000001, -0.38, 0.58999999999999997, -0.60999999999999999, 0.19, -0.93000000000000005, -0.40000000000000002, 0.14000000000000001, -0.75, -0.050000000000000003, -0.20999999999999999, 0.23000000000000001, 0.20999999999999999, -0.47999999999999998, -0.63, 0.42999999999999999, -0.59999999999999998, -0.68999999999999995, -0.47999999999999998, -0.39000000000000001, 0.050000000000000003, -0.68000000000000005, 0.029999999999999999, 0, -0.41999999999999998, -0.56000000000000005, -0.73999999999999999, -0.31, -0.48999999999999999, -0.79000000000000004, 0.22, 0.10000000000000001, -0.51000000000000001, -0.20999999999999999, -0.75, -0.81999999999999995, -0.31, 0.41999999999999998, 0.12, -0.79000000000000004, -0.81999999999999995, -0.46000000000000002, -0.44, -0.40000000000000002, 0.14000000000000001, -0.53000000000000003, -0.53000000000000003, -0.56000000000000005, -0.02, 0.20999999999999999, -0.35999999999999999, -0.72999999999999998, -0.78000000000000003, -0.68000000000000005, -0.81000000000000005, -0.5, -0.54000000000000004, -0.48999999999999999, -0.46999999999999997, -0.46000000000000002, -0.20999999999999999, 0.40999999999999998, -0.20000000000000001, -0.54000000000000004, 0.20000000000000001, -0.37, -0.42999999999999999, 0.37, -0.85999999999999999, -0.02, -0.56000000000000005, -0.79000000000000004, 0.52000000000000002, -0.79000000000000004, -0.85999999999999999, -0.79000000000000004, -0.01, 0.080000000000000002, -0.63, -0.68000000000000005, -0.27000000000000002, -0.16, -0.68000000000000005, -0.040000000000000001, -0.46000000000000002, -0.73999999999999999, -0.29999999999999999, -0.56999999999999995, -0.82999999999999996, -0.37, -0.71999999999999997, -0.37, -0.67000000000000004, -0.46000000000000002, -0.040000000000000001, -0.17000000000000001, -0.050000000000000003, -0.32000000000000001, 0.31, -0.68000000000000005, 0.12, -0.52000000000000002, -0.81000000000000005, -0.059999999999999998, -0.01, -0.69999999999999996, -0.070000000000000007, -0.82999999999999996, 0.16, -0.29999999999999999, -0.38, -0.44, -0.27000000000000002, 0.16, -0.01, -0.27000000000000002, -0.48999999999999999, -0.58999999999999997, -0.92000000000000004, -0.72999999999999998, -0.34999999999999998, -0.089999999999999997, -0.01, -0.34000000000000002, -0.40999999999999998, -0.58999999999999997, -0.059999999999999998, -0.45000000000000001, -0.40000000000000002, -0.42999999999999999, -0.32000000000000001, -0.69999999999999996, -0.72999999999999998, -0.91000000000000003, -0.089999999999999997, -0.52000000000000002, -0.28999999999999998, -0.23000000000000001, -0.93000000000000005, -0.01, -0.19, -0.19, 0.02, -0.92000000000000004, -0.91000000000000003, 0.029999999999999999, -0.81999999999999995, -0.28999999999999998, -0.19, -0.23999999999999999, 0.17000000000000001, -0.54000000000000004, -0.35999999999999999, -0.17000000000000001, -0.54000000000000004, -0.10000000000000001, -0.59999999999999998, -0.28999999999999998, -0.37, 0.26000000000000001, -0.84999999999999998, -0.51000000000000001, -0.80000000000000004, -0.13, 0.29999999999999999, -0.67000000000000004, -0.82999999999999996, 0.050000000000000003, -0.050000000000000003, 0.47999999999999998, -0.45000000000000001, -0.39000000000000001, -0.32000000000000001, 0.17000000000000001, -0.47999999999999998, 0.20000000000000001, -0.40999999999999998, -0.71999999999999997, -0.48999999999999999, -0.26000000000000001, -0.23999999999999999, 0.089999999999999997, -0.83999999999999997, -0.040000000000000001, -0.89000000000000001, 0.14000000000000001, -0.45000000000000001, -0, -0.47999999999999998, 0.33000000000000002, -0.73999999999999999, -0.53000000000000003, -0.68999999999999995, -0.070000000000000007, -0.040000000000000001, -0.26000000000000001, -0.56000000000000005, -0.5, 0.75, -0.73999999999999999, -0.87, -0.80000000000000004, -0.58999999999999997, -0.13, 0.25, -0.68999999999999995, -0.69999999999999996, -0.70999999999999996, -0.75, -0.56999999999999995, 0.070000000000000007, -0.28999999999999998, 0.60999999999999999, -0.080000000000000002, -0.63, -0.56000000000000005, -0.76000000000000001, -0.59999999999999998, -0.13, -0.72999999999999998, -0.5, -0.75, -0.14999999999999999, -0.66000000000000003, 0.51000000000000001, -0.41999999999999998, -0.26000000000000001, -0.23000000000000001, -0.5, 0.080000000000000002, -0.42999999999999999, -0.60999999999999999, -0.5, 0.11, -0.76000000000000001, -0.26000000000000001, -0.26000000000000001, -0.65000000000000002, -0.46999999999999997, -0.82999999999999996, -0.32000000000000001, -0.70999999999999996, -0.46000000000000002, -0.17999999999999999, -0.27000000000000002, -0.78000000000000003, -0.82999999999999996, 0.34000000000000002, -0.40999999999999998, -0.39000000000000001, -0.44, 0.26000000000000001, -0.88, -0.90000000000000002, -0.68999999999999995, -0.51000000000000001, -0.5, -0.95999999999999996, -0.68999999999999995, -0.65000000000000002, -0.48999999999999999, 0.059999999999999998, -0.46000000000000002, -0.77000000000000002, -0.040000000000000001, -0.58999999999999997, 0.5, -0.60999999999999999, -0.54000000000000004, -0.48999999999999999, -0.64000000000000001, -0.54000000000000004, 0.41999999999999998, -0.63, -0.16, 0.02, -0.5, -0.17000000000000001, 0.16, -0.81999999999999995, -0.89000000000000001, -0.46999999999999997, -0.67000000000000004, -0.31, -0.28999999999999998, -0.050000000000000003, -0.75, -0.22, -0.45000000000000001, -0.46000000000000002, 0.35999999999999999, 0.13, 0.14000000000000001, -0.16, -0.62, -0.55000000000000004, -0.44, -0.28000000000000003, -0.37, -0.59999999999999998, 0.070000000000000007, 0.82999999999999996, -0.89000000000000001, -0.42999999999999999, -0.57999999999999996, -0.64000000000000001, -0.65000000000000002, -0.40000000000000002, -0.57999999999999996, -0.19, -0.54000000000000004, -0.63, -0.34999999999999998, -0.070000000000000007, -0.35999999999999999, -0.77000000000000002, 0.080000000000000002, -0.51000000000000001, -0.57999999999999996, -0.089999999999999997, -0.35999999999999999, -0.42999999999999999, -0.75, -0.25, -0.66000000000000003, -0.34000000000000002, 0.10000000000000001, 0.31, -0.20999999999999999, -0.62, -0.17000000000000001, -0.70999999999999996, -0.67000000000000004, 0.11, -0.53000000000000003, 0.029999999999999999, 0.42999999999999999, 0.070000000000000007, 0.13, -0.34000000000000002, -0.64000000000000001, -0.62, -0.56999999999999995, -0.01, 0.37, -0.53000000000000003, -0.62, -0.040000000000000001, 0.16, 0.080000000000000002, -0.46000000000000002, -0.59999999999999998, 0.13, -0.80000000000000004, -0.41999999999999998, -0.41999999999999998, -0.34000000000000002, -0.23999999999999999, -0.58999999999999997, 0.20999999999999999, -0.01, 0.63, -0.84999999999999998, -0.45000000000000001, -0.050000000000000003, -0.80000000000000004, 0.26000000000000001, -0.81999999999999995, -0.35999999999999999, -0.51000000000000001, -0.22, -0.14000000000000001, 0.29999999999999999, -0.47999999999999998, -0.38, -0.059999999999999998, -0.81000000000000005, -0.81000000000000005, -0.33000000000000002, -0.29999999999999999, -0.39000000000000001, -0.31, -0.11, 0.17999999999999999, -0.12, -0.080000000000000002, -0.48999999999999999, -0.42999999999999999, -0.66000000000000003, -0.93999999999999995, -0.40999999999999998, -0.64000000000000001, -0.87, -0.39000000000000001, -0.41999999999999998, -0.17999999999999999, -0.22, -0.40000000000000002, -0.48999999999999999, -0.84999999999999998, -0.17000000000000001, -0.53000000000000003, -0.59999999999999998, -0.31, -0.32000000000000001, -0.78000000000000003, -0.63, -0.20999999999999999, -0.11, -0.12, -0.54000000000000004, -0.33000000000000002, -0.42999999999999999, -0.69999999999999996, -0.17000000000000001, -0.94999999999999996, -0.16, -0.76000000000000001, -0.72999999999999998, -0.40999999999999998, -0.29999999999999999, -0.17000000000000001, -0.62, 0.11, -0.070000000000000007, -0.52000000000000002, -0.89000000000000001, -0.78000000000000003, -0.35999999999999999, -0.41999999999999998, -0.44, -0.53000000000000003, -0.22, -0.080000000000000002, -0.23000000000000001, -0.39000000000000001, -0.78000000000000003, -0.89000000000000001, -0.66000000000000003, -0.58999999999999997, -0.31, -0.76000000000000001, -0.73999999999999999, -0.11, -0.70999999999999996, 0.20000000000000001, -0.52000000000000002, 0.48999999999999999, -0.080000000000000002, -0.13, -0.17999999999999999, -0.69999999999999996, 0.02, 0.31, 0.55000000000000004, -0.58999999999999997, -0.57999999999999996, -0.41999999999999998, -0.87, -0.25, -0.14999999999999999, -0.40999999999999998, -0.089999999999999997, -0.22, -0.59999999999999998, -0.64000000000000001, 0.050000000000000003, -0.01, -0.44, -0.12, 0.17000000000000001, -0.22, -0.26000000000000001, -0.92000000000000004, 0.14000000000000001, 0.68999999999999995, -0.01, 0.13, -0.23000000000000001, -0.040000000000000001, -0.56000000000000005, 0.040000000000000001, -0.68000000000000005, -0.78000000000000003, -0.14999999999999999, -0.68000000000000005, -0.37, -0.38, -0.080000000000000002, -0.32000000000000001, -0.45000000000000001, -0.20000000000000001, -0.55000000000000004, -0.93000000000000005, 0.38, -0.14000000000000001, -0.040000000000000001, -0.39000000000000001, -0.42999999999999999, -0.71999999999999997, -0.82999999999999996, -0.10000000000000001, -0.42999999999999999, 0.089999999999999997, -0.28000000000000003, 0.17999999999999999, -0.46000000000000002, -0.20000000000000001, 0.47999999999999998, 0.13, -0.82999999999999996, -0.089999999999999997, -0.20999999999999999, -0.02, -0.90000000000000002, -0.16, -0.20999999999999999, -0.089999999999999997, -0.41999999999999998, -0.14000000000000001, -0.64000000000000001, 0.53000000000000003, -0.55000000000000004, -0.01, -0.54000000000000004, -0.28999999999999998, -0.64000000000000001, -0.59999999999999998, 0.040000000000000001, 0.10000000000000001, 0.040000000000000001, -0.040000000000000001, -0.48999999999999999, -0.40000000000000002, -0.35999999999999999, -0.81999999999999995, -0.02, -0.53000000000000003, -0.59999999999999998, -0.029999999999999999, -0.27000000000000002, -0.60999999999999999, 0.34999999999999998, -0.26000000000000001, -0.33000000000000002, -0.44, -0.72999999999999998, 0.17000000000000001, -0.55000000000000004, -0.17000000000000001, -0.23999999999999999, -0.37, -0.10000000000000001, -0.28999999999999998, -0.34999999999999998, -0.11, -0.93999999999999995, -0.040000000000000001, -0.64000000000000001, -0.34000000000000002, -0.29999999999999999, -0.92000000000000004, -0.81000000000000005, -0.72999999999999998, -0.41999999999999998, -0.68000000000000005, -0.73999999999999999, -0.59999999999999998, -0.73999999999999999, 0.51000000000000001, 0.14000000000000001, -0.80000000000000004, -0.28999999999999998, -0.64000000000000001, -0.23999999999999999, -0.93000000000000005, -0.46999999999999997, -0.93999999999999995, -0.89000000000000001, -0.70999999999999996, -0.34000000000000002, -0.90000000000000002, -0.23000000000000001, -0.33000000000000002, -0.48999999999999999, 0.059999999999999998, -0.45000000000000001, 0.25, -0.76000000000000001, -0.12, -0.55000000000000004, -0.059999999999999998, -0.80000000000000004, -0.23999999999999999, 0.22, 0.39000000000000001, -0.53000000000000003, -0.41999999999999998, -0.40999999999999998, -0.02, -0.78000000000000003, -0.13, -0.23000000000000001, -0.28000000000000003, -0.40999999999999998, -0.55000000000000004, -0.46999999999999997, -0.40999999999999998, 0.39000000000000001, -0.90000000000000002, -0.10000000000000001, -0.54000000000000004, -0.42999999999999999, 0.47999999999999998, -0.19, -0.81999999999999995, -0.12, -0.37, 0.080000000000000002, -0.040000000000000001, -0.17000000000000001, -0.34999999999999998, -0.83999999999999997, -0.56999999999999995, -0.16, -0.69999999999999996, -0.25, -0.40000000000000002, 0.20999999999999999, -0.38, -0.27000000000000002, -0.57999999999999996, -0.46000000000000002, -0.52000000000000002, -0.11, -0.01, -0.080000000000000002, -0.51000000000000001, -0.53000000000000003, -0.79000000000000004, 0.47999999999999998, -0.38, -0.90000000000000002, 0.34999999999999998, -0.17999999999999999, -0.55000000000000004, -0.60999999999999999, -0.089999999999999997, -0.40999999999999998, -0.17999999999999999, -0.54000000000000004, -0.82999999999999996, 0.14999999999999999, -0.26000000000000001, 0.17999999999999999, -0.48999999999999999, -0.55000000000000004, -0.29999999999999999, -0.20999999999999999, -0.63, -0.46999999999999997, -0.57999999999999996], + "configs": { + "default": { + "c": 0, + "masspoints": "adjust", + "kernel": "tri", + "p": 1, + "q": 2, + "bwselect": "mserd", + "fuzzy_in": true, + "sharpbw": false, + "h_in": null, + "b_in": null, + "rho_in": null, + "level": 95, + "h_l": 0.28612984883490244, + "h_r": 0.28612984883490244, + "b_l": 0.44130048765237834, + "b_r": 0.44130048765237834, + "tau_cl": 1.3404167025226903, + "tau_bc": 1.3439264669339765, + "se_cl": 0.12277436357656329, + "se_rb": 0.14492124355720437, + "z": [10.917724706321065, 10.946311817742723, 9.2734952719577795], + "pv": [9.4841963446026395e-28, 6.9208534453303803e-28, 1.801437140261924e-20], + "ci_lower": [1.0997833716878, 1.1032931360990863, 1.0598860489670987], + "ci_upper": [1.5810500333575805, 1.5845597977688668, 1.6279668849008544], + "N": [1254, 246], + "N_h": [355, 170], + "N_b": [618, 218], + "bias": [-0.01961408515471777, -0.023123849566004013], + "beta_p_l": [0.18524898835118259, 0.8165796782810264], + "beta_p_r": [0.79574981603819372, 1.1363227333792263], + "tau_T": [0.45545599852496377, 0.43643224882147913, 0.43643224882147913], + "se_T": [0.093094915971961151, 0.093094915971961151, 0.10984448811476449], + "z_T": [4.8923831529332986, 4.6880352623437167, 3.9731829635866553], + "pv_T": [9.9622262080311173e-07, 2.7584049654383495e-06, 7.0918531602339176e-05], + "ci_T_lower": [0.2729933160761373, 0.25396956637265267, 0.22114100821630278], + "ci_T_upper": [0.63791868097379023, 0.61889493127030559, 0.65172348942665548], + "beta_t_p_l": [0.17256960574023161, 0.17777033741739817], + "beta_t_p_r": [0.62802560426519538, 0.7049071944937696] + }, + "sharpbw_true": { + "c": 0, + "masspoints": "adjust", + "kernel": "tri", + "p": 1, + "q": 2, + "bwselect": "mserd", + "fuzzy_in": true, + "sharpbw": true, + "h_in": null, + "b_in": null, + "rho_in": null, + "level": 95, + "h_l": 0.21699801013882328, + "h_r": 0.21699801013882328, + "b_l": 0.32240427518853387, + "b_r": 0.32240427518853387, + "tau_cl": 1.3347715715239019, + "tau_bc": 1.3314142488694134, + "se_cl": 0.13639528044419613, + "se_rb": 0.16371631306726853, + "z": [9.7860539395276334, 9.761439285387441, 8.1324470599478733], + "pv": [1.2924168779801247e-22, 1.6479954842733714e-22, 4.2070994854064479e-16], + "ci_lower": [1.0674417341920373, 1.0640844115375487, 1.0105361715758829], + "ci_upper": [1.6021014088557666, 1.5987440862012781, 1.6522923261629439], + "N": [1254, 246], + "N_h": [261, 146], + "N_b": [419, 181], + "bias": [-0.0022779404185279246, 0.0010793822359604455], + "beta_p_l": [0.20095886807827323, 1.0652409849358579], + "beta_p_r": [0.80351105780740573, 0.99978493517598266], + "tau_T": [0.45142719742015613, 0.44906804862752464, 0.44906804862752464], + "se_T": [0.1044134802247072, 0.1044134802247072, 0.1258675081967762], + "z_T": [4.3234570521798927, 4.3008627589185782, 3.5677837359381877], + "pv_T": [1.536030514620781e-05, 1.7013440968775077e-05, 0.00036001344673183827], + "ci_T_lower": [0.24678053667924493, 0.24442138788661344, 0.20237226573804334], + "ci_T_upper": [0.65607385816106734, 0.6537147093684359, 0.69576383151700594], + "beta_t_p_l": [0.18276560828250166, 0.3335707502882837], + "beta_t_p_r": [0.63419280570265779, 0.6024818592461525] + }, + "manual_h": { + "c": 0, + "masspoints": "adjust", + "kernel": "tri", + "p": 1, + "q": 2, + "bwselect": "mserd", + "fuzzy_in": true, + "sharpbw": false, + "h_in": 0.20000000000000001, + "b_in": null, + "rho_in": null, + "level": 95, + "h_l": 0.20000000000000001, + "h_r": 0.20000000000000001, + "b_l": 0.20000000000000001, + "b_r": 0.20000000000000001, + "tau_cl": 1.3408433878030526, + "tau_bc": 1.4657244157186455, + "se_cl": 0.14057852089763265, + "se_rb": 0.20475143450039301, + "z": [9.5380388073611648, 10.426375283788666, 7.1585550513729475], + "pv": [1.4555885133255463e-21, 1.879207597384411e-25, 8.1531846636887632e-13], + "ci_lower": [1.0653145498437813, 1.1901955777593742, 1.0644189783149636], + "ci_upper": [1.6163722257623239, 1.7412532536779168, 1.8670298531223275], + "N": [1254, 246], + "N_h": [235, 138], + "N_b": [235, 138], + "bias": [0.046240079825857594, -0.078640948089735388], + "beta_p_l": [0.20197072845933073, 1.0854798166073323], + "beta_p_r": [0.80827477436040895, 0.90480076157652345], + "tau_T": [0.45218110587433802, 0.51719616539200819, 0.51719616539200819], + "se_T": [0.10820108117731556, 0.10820108117731556, 0.15993789062748404], + "z_T": [4.1790812157719746, 4.7799537653828805, 3.2337313150929616], + "pv_T": [2.926891563994721e-05, 1.7533551159407838e-06, 0.0012218434554680311], + "ci_T_lower": [0.24011088367850486, 0.30512594319617503, 0.2037236599988333], + "ci_T_upper": [0.66425132807017118, 0.72926638758784135, 0.83066867078518314], + "beta_t_p_l": [0.18440437001680113, 0.36742878819041508], + "beta_t_p_r": [0.63658547589113912, 0.55441952017377993] + }, + "epa": { + "c": 0, + "masspoints": "adjust", + "kernel": "epa", + "p": 1, + "q": 2, + "bwselect": "mserd", + "fuzzy_in": true, + "sharpbw": false, + "h_in": null, + "b_in": null, + "rho_in": null, + "level": 95, + "h_l": 0.30775222057362539, + "h_r": 0.30775222057362539, + "b_l": 0.51823837659882865, + "b_r": 0.51823837659882865, + "tau_cl": 1.3430984195763143, + "tau_bc": 1.3385288051831172, + "se_cl": 0.11651005610261295, + "se_rb": 0.13767274743507457, + "z": [11.527746741392161, 11.488525968987997, 9.7225400823380621], + "pv": [9.5613475078509514e-31, 1.5066069083397342e-30, 2.416761079397009e-22], + "ci_lower": [1.1147429057784519, 1.1101732913852549, 1.0686951785576921], + "ci_upper": [1.5714539333741766, 1.5668843189809796, 1.6083624318085423], + "N": [1254, 246], + "N_h": [395, 177], + "N_b": [725, 233], + "bias": [-0.018654053621504407, -0.014084439228307481], + "beta_p_l": [0.1765646654858162, 0.72795943168904065], + "beta_p_r": [0.79188704198317328, 1.16664530161309], + "tau_T": [0.45813647572563077, 0.43891075669685231, 0.43891075669685231], + "se_T": [0.086874530506082306, 0.086874530506082306, 0.10153840887335157], + "z_T": [5.2735418891680279, 5.0522374525652607, 4.3226081791798006], + "pv_T": [1.3381569266480424e-07, 4.3666436103082304e-07, 1.5419543461291671e-05], + "ci_T_lower": [0.28786552475988325, 0.2686398057311048, 0.23989913225758105], + "ci_T_upper": [0.62840742669137828, 0.60918170766259983, 0.63792238113612354], + "beta_t_p_l": [0.16608873326710291, 0.11268138875383764], + "beta_t_p_r": [0.6242252089927337, 0.74132201482348203] + }, + "msetwo": { + "c": 0, + "masspoints": "adjust", + "kernel": "tri", + "p": 1, + "q": 2, + "bwselect": "msetwo", + "fuzzy_in": true, + "sharpbw": false, + "h_in": null, + "b_in": null, + "rho_in": null, + "level": 95, + "h_l": 0.23468206705181063, + "h_r": 0.24896032552463673, + "b_l": 0.35975019468582764, + "b_r": 0.38231021975698526, + "tau_cl": 1.3329710851975234, + "tau_bc": 1.3326685257637381, + "se_cl": 0.13158803544032455, + "se_rb": 0.15513457724564825, + "z": [10.129880583269507, 10.127581290383395, 8.5904029225768319], + "pv": [4.0714636754353655e-24, 4.1683249404631608e-24, 8.6664866956800003e-18], + "ci_lower": [1.075063274938107, 1.0747607155043217, 1.0286103416054206], + "ci_upper": [1.5908788954569397, 1.5905763360231544, 1.6367267099220555], + "N": [1254, 246], + "N_h": [285, 159], + "N_b": [465, 202], + "bias": [-0.0095734816012768027, -0.009270922167491492], + "beta_p_l": [0.19840209492345862, 1.0171662842515934], + "beta_p_r": [0.79686251190958668, 1.1201029210450977], + "tau_T": [0.44896729091272564, 0.4297622904438162, 0.4297622904438162], + "se_T": [0.099509384795340675, 0.099509384795340675, 0.11759920488213192], + "z_T": [4.5118085277696105, 4.318811651058855, 3.6544659538689999], + "pv_T": [6.4277200352736546e-06, 1.568715855424927e-05, 0.00025771785765190536], + "ci_T_lower": [0.2539324805901203, 0.23472748012121089, 0.19927208426429083], + "ci_T_upper": [0.64400210123533097, 0.62479710076642148, 0.66025249662334162], + "beta_t_p_l": [0.18238180939345988, 0.32622267450404013], + "beta_t_p_r": [0.63134910030618552, 0.65376217547086013] + }, + "one_sided": { + "c": 0, + "masspoints": "adjust", + "kernel": "tri", + "p": 1, + "q": 2, + "bwselect": "mserd", + "fuzzy_in": true, + "sharpbw": false, + "h_in": null, + "b_in": null, + "rho_in": null, + "level": 95, + "h_l": 0.21699801013882328, + "h_r": 0.21699801013882328, + "b_l": 0.32240427518853387, + "b_r": 0.32240427518853387, + "tau_cl": 0.95010883805522062, + "tau_bc": 0.92926782436143707, + "se_cl": 0.14678365057408849, + "se_rb": 0.17284239694442816, + "z": [6.4728519446084825, 6.3308673733549954, 5.376388205610299], + "pv": [9.6170176922431624e-11, 2.4378683020091905e-10, 7.5994847786735253e-08], + "ci_lower": [0.6624181694106952, 0.64157715571691165, 0.59050295134878206], + "ci_upper": [1.237799506699746, 1.2169584930059625, 1.2680326973740921], + "N": [1254, 246], + "N_h": [261, 146], + "N_b": [419, 181], + "bias": [-0.025532650763975179, -0.0046916370701917485], + "beta_p_l": [0.20095886807827323, 1.0652409849358579], + "beta_p_r": [0.80351105780740573, 0.99978493517598266], + "tau_T": [0.63419280570265779, 0.64319462644932535, 0.64319462644932535], + "se_T": [0.08974436513779252, 0.08974436513779252, 0.10920351299723868], + "z_T": [7.0666587782856904, 7.1669639142443238, 5.8898712028210038], + "pv_T": [1.5870855918146489e-12, 7.6679233146751593e-13, 3.8649672341259728e-09], + "ci_T_lower": [0.45829708221717247, 0.46729890296384002, 0.4291596739894859], + "ci_T_upper": [0.81008852918814311, 0.81909034993481067, 0.85722957890916485], + "beta_t_p_l": [0, 0], + "beta_t_p_r": [0.63419280570265779, 0.6024818592461525] + }, + "ties_adjust": { + "c": 0, + "masspoints": "adjust", + "kernel": "tri", + "p": 1, + "q": 2, + "bwselect": "mserd", + "fuzzy_in": true, + "sharpbw": false, + "h_in": null, + "b_in": null, + "rho_in": null, + "level": 95, + "h_l": 0.20714942836244607, + "h_r": 0.20714942836244607, + "b_l": 0.33671820903690547, + "b_r": 0.33671820903690547, + "tau_cl": 1.2029408375164861, + "tau_bc": 1.1583077553671122, + "se_cl": 0.17856937362089786, + "se_rb": 0.20600990853963363, + "z": [6.7365462123999222, 6.4865980760295177, 5.6225827368117525], + "pv": [1.6219572366827823e-11, 8.7796136577574806e-11, 1.8812341657456412e-08], + "ci_lower": [0.85295129647764956, 0.80831821432827566, 0.7545357541710398], + "ci_upper": [1.5529303785553226, 1.5082972964059487, 1.5620797565631845], + "N": [1248, 252], + "N_h": [235, 146], + "N_b": [428, 192], + "bias": [-0.021267149822323108, 0.023365932327050535], + "beta_p_l": [0.22528735519667978, 1.2877738683606959], + "beta_p_r": [0.65565733477629518, 2.4217908146895502], + "tau_T": [0.35776487600847395, 0.32864349658334491, 0.32864349658334491], + "se_T": [0.09934104223696355, 0.09934104223696355, 0.11458005347216932], + "z_T": [3.6013803353812022, 3.3082348361054419, 2.8682435260267187], + "pv_T": [0.00031653211127909982, 0.00093886046373515913, 0.0041275769549705436], + "ci_T_lower": [0.16306001103735313, 0.13393863161222408, 0.10407071843121954], + "ci_T_upper": [0.55246974097959478, 0.52334836155446574, 0.5532162747354703], + "beta_t_p_l": [0.18644814451619884, 0.37173981630424729], + "beta_t_p_r": [0.5442130205246728, 1.4746780945678835] + } + } + }, "senate": { "csv": "benchmarks/data/rdrobust_senate.csv", "configs": { @@ -459,6 +777,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, @@ -489,6 +809,8 @@ "p": 1, "q": 2, "bwselect": "mserd", + "fuzzy_in": false, + "sharpbw": false, "h_in": null, "b_in": null, "rho_in": null, diff --git a/diff_diff/_rdrobust_port.py b/diff_diff/_rdrobust_port.py index fed721ae..e452277e 100644 --- a/diff_diff/_rdrobust_port.py +++ b/diff_diff/_rdrobust_port.py @@ -1,7 +1,9 @@ -"""In-house port of rdrobust's sharp-RD bandwidth-selection machinery. +"""In-house port of rdrobust's RD bandwidth-selection and estimation +machinery - sharp and fuzzy paths. -Faithful Python translation of the sharp-RD branch (no fuzzy / covariates / -cluster / weights) of ``rdbwselect`` from the R package ``rdrobust`` 4.0.0, +Faithful Python translation of the sharp and fuzzy no-covariate/no-cluster +``nn`` branches of ``rdbwselect`` and ``rdrobust`` from the R package +``rdrobust`` 4.0.0, ported from the CRAN source tarball (sha256 below), cross-checked against ``deparse(getFromNamespace(, "rdrobust"))`` of the installed 4.0.0 package. The unreleased GitHub development tree (4.1.0-dev) differs from @@ -21,11 +23,11 @@ ``compute_dups_dupsid(x_sorted)`` rle blocks (rdbwselect.R:322-327) ``rdrobust_res_nn(...)`` ``rdrobust_res`` vce="nn" branch (functions.R:146-181) -``rdrobust_vce_sharp(RX, res)`` ``rdrobust_vce`` null-cluster d==0 +``rdrobust_vce(RX, res)`` ``rdrobust_vce`` null-cluster d==0 branch (functions.R:374-378) ``rdrobust_bw(...)`` ``rdrobust_bw`` sharp path (functions.R:207-355) -``rdbwselect_sharp(...)`` ``rdbwselect`` main flow +``rdbwselect(...)`` ``rdbwselect`` main flow (rdbwselect.R; anchors inline) ========================================== =================================== @@ -78,12 +80,12 @@ "rdrobust_vander", "compute_dups_dupsid", "rdrobust_res_nn", - "rdrobust_vce_sharp", + "rdrobust_vce", "rdrobust_bw", - "rdbwselect_sharp", + "rdbwselect", "quantile_type2", - "RdFitSharpResult", - "rdrobust_fit_sharp", + "RdFitResult", + "rdrobust_fit", ] # Upstream pin: CRAN source tarball of record. rdrobust has no git SHA on @@ -212,19 +214,25 @@ def rdrobust_res_nn( matches: int, dups: np.ndarray, dupsid: np.ndarray, + t: Optional[np.ndarray] = None, ) -> np.ndarray: - """Nearest-neighbor variance residuals, sharp outcome-only case - (functions.R:146-181, ``vce == "nn"`` branch). + """Nearest-neighbor variance residuals (functions.R:146-181, + ``vce == "nn"`` branch). Abadie-Imbens NN sigma via same-side neighbors on the SORTED ``x``. Ties are matched as whole ``dups``/``dupsid`` blocks; distances compare EXACTLY (4.0.0 semantics - the 4.1.0-dev ``nn_tol`` tolerance is deliberately absent). Equal left/right distances expand BOTH directions (functions.R:162-165). Returns the (n,) residual vector - ``sqrt(J/(J+1)) * (y_i - mean(y_neighbors))``. + ``sqrt(J/(J+1)) * (y_i - mean(y_neighbors))`` for the sharp + outcome-only case, or the (n, 2) residual matrix with the fuzzy + take-up column ``sqrt(J/(J+1)) * (t_i - mean(t_neighbors))`` appended + when ``t`` is supplied (functions.R:171-174; T shares Y's neighbor + sets exactly - both depend only on ``x``). """ n = y.shape[0] - res = np.empty(n, dtype=np.float64) + fuzzy = t is not None + res = np.empty((n, 2) if fuzzy else n, dtype=np.float64) limit = min(matches, n - 1) for pos in range(n): # R pos is 1-based; comments track R indices rpos = int(dups[pos] - dupsid[pos]) @@ -255,17 +263,72 @@ def rdrobust_res_nn( # subset; the effective window is [lo, hi] inclusive around pos. y_J = float(np.sum(y[lo : hi + 1])) - float(y[pos]) Ji = (hi - lo + 1) - 1 - res[pos] = np.sqrt(Ji / (Ji + 1)) * (y[pos] - y_J / Ji) + r_y = np.sqrt(Ji / (Ji + 1)) * (y[pos] - y_J / Ji) + if fuzzy: + assert t is not None + t_J = float(np.sum(t[lo : hi + 1])) - float(t[pos]) # functions.R:172 + res[pos, 0] = r_y + res[pos, 1] = np.sqrt(Ji / (Ji + 1)) * (t[pos] - t_J / Ji) + else: + res[pos] = r_y return res -def rdrobust_vce_sharp(RX: np.ndarray, res: np.ndarray) -> np.ndarray: - """Variance meat, sharp no-cluster case (functions.R:374-378, d==0): - ``M = crossprod(res * RX)``.""" - scaled = res[:, None] * RX +def rdrobust_vce(RX: np.ndarray, res: np.ndarray, s: Optional[np.ndarray] = None) -> np.ndarray: + """Variance meat, no-cluster case (functions.R:374-385). + + Sharp (``s=None``, d==0): ``M = crossprod(res * RX)`` with the (n,) + residual vector. Fuzzy (d>0): the (n, 1+d) residual matrix is collapsed + by the linear-combination vector first, ``r_comb = res %*% s``, then + ``M = crossprod(r_comb * RX)`` - the Y-T covariance materializes in the + ``2*s[0]*s[1]*res_Y*res_T`` cross term (functions.R:379-385). ``s`` is + the delta-method vector ``s_Y`` for the ratio variance or the selector + ``sV_T = [0, 1]`` for the first-stage variance. + """ + if s is None: + scaled = res[:, None] * RX + else: + r_comb = res @ s + scaled = r_comb[:, None] * RX return scaled.T @ scaled +def _var0(v: np.ndarray) -> bool: + """R's exact ``var(T_side) == 0`` check (rdrobust.R:179). A + single-element side is treated as no-variation: R's ``var()`` would be + NA there and crash the ``if()`` opaquely; zero variation is the + semantically correct fail-closed reading and feeds the same + perf_comp/identification logic. Implemented as exact constancy rather + than ``np.var(...) == 0.0``: R's two-pass ``mean()`` makes its var of + a constant vector exactly zero, while numpy's single-pass mean leaves + ~1e-32 roundoff for constants like 0.7 - the exact-constancy test is + the faithful translation (var == 0 iff constant).""" + return v.shape[0] < 2 or bool(np.all(v == v[0])) + + +def _fuzzy_identification_stop(t_l: np.ndarray, t_r: np.ndarray) -> None: + """Fuzzy identification guard, R-exact condition and message + (rdrobust.R:175-177 == rdbwselect.R:339-341): reject only the FULLY + degenerate first stage - zero variance on BOTH sides AND no mean jump + at the cutoff. One-sided zero variance is a legitimate design + (one-sided perfect compliance -> perf_comp bandwidth switch).""" + if t_l.shape[0] == 0 or t_r.shape[0] == 0: + # An empty side is the one-sided-data failure, not a first-stage + # identification failure - defer to the targeted one-sided error + # downstream instead of np.mean-ing an empty array here. + return + if ( + _var0(t_l) + and _var0(t_r) + and abs(float(np.mean(t_l)) - float(np.mean(t_r))) + < float(np.sqrt(np.finfo(np.float64).eps)) + ): + raise ValueError( + "Fuzzy RD: first-stage variable has no variation and no jump " + "at the cutoff. The fuzzy estimator is not identified." + ) + + @dataclass class _BwPilot: """Per-side pilot block returned by :func:`rdrobust_bw` @@ -292,15 +355,27 @@ def rdrobust_bw( kernel: str, dups: np.ndarray, dupsid: np.ndarray, - vcache: Optional[Dict[str, Tuple[float, float]]] = None, + t: Optional[np.ndarray] = None, + vcache: Optional[Dict[str, Tuple[float, float, Optional[np.ndarray]]]] = None, ) -> _BwPilot: - """Per-side pilot V/B(/R) block (functions.R:207-355, sharp path). - - Sharp specialization: T = Z = C = W = NULL so the fuzzy/covariate - combination vector ``s`` is the scalar 1 (functions.R:234) and the - response matrix is the outcome column alone. ``vcache`` shares the - fixed-``h_V`` V-fit across pilot calls keyed on ``(o, nu)`` - (functions.R:216-222), matching R's per-side environment cache. + """Per-side pilot V/B(/R) block (functions.R:207-355, no-covariate + sharp and fuzzy paths). + + Sharp (``t=None``): Z = C = W = NULL so the combination vector ``s`` + is the scalar 1 (functions.R:234) and the response is the outcome + column alone. Fuzzy: T is stacked as a second response column into + BOTH the V-fit and B-fit designs (functions.R:236-240, 315-318) and + the pilot ratio + delta vector ``s = [1/tau_T, -tau_Y/tau_T^2]`` is + computed from the V-fit coefficients (functions.R:264-268), then + threaded into the V/B variance meats and the bias constant + ``t(s) %*% beta_B[o+2,]`` (functions.R:294, 346, 349). A pilot window + with no take-up variation makes ``tau_T == 0``; the division follows + R's Inf/NaN flow-on (numpy float under ``errstate``) and the + downstream stage assembly fails closed on the non-finite bandwidth. + ``vcache`` shares the fixed-``h_V`` V-fit across pilot calls keyed on + ``(o, nu)`` (functions.R:216-222) and stores ``(V_V, BConst, s)`` - + the cached ``V_V`` embeds the fuzzy ``s``, so ``s`` must be reused on + cache hits exactly as R's environment cache does. """ if vce != "nn": raise NotImplementedError( @@ -308,8 +383,9 @@ def rdrobust_bw( "variance modes are a documented seam." ) key = f"{o}_{nu}" + s: Optional[np.ndarray] if vcache is not None and key in vcache: - V_V, BConst = vcache[key] # functions.R:218-222 + V_V, BConst, s = vcache[key] # functions.R:218-222 else: # --- V-fit at (o, nu), bandwidth h_V (functions.R:226-299) --- w = rdrobust_kweight(x, c, h_V, kernel) @@ -319,17 +395,35 @@ def rdrobust_bw( eW = w[ind_V] R_V = rdrobust_vander(eX - c, o) invG_V = qrXXinv(R_V * np.sqrt(eW)[:, None]) - # R computes beta_V here (functions.R:263) but the sharp/nn path - # never consumes it (it feeds the fuzzy ratio and hc predictions); - # omitted - no numeric effect on V, B, or R. - res_V = rdrobust_res_nn(eX, eY, nnmatch, dups[ind_V], dupsid[ind_V]) # functions.R:293 - aux = rdrobust_vce_sharp(R_V * eW[:, None], res_V) # functions.R:294 + if t is None: + # R computes beta_V here (functions.R:263) but the sharp/nn + # path never consumes it (it feeds the fuzzy ratio and hc + # predictions); omitted - no numeric effect on V, B, or R. + s = None + res_V = rdrobust_res_nn(eX, eY, nnmatch, dups[ind_V], dupsid[ind_V]) # functions.R:293 + else: + eT = t[ind_V] # functions.R:236-240 + D_V = np.column_stack([eY, eT]) + beta_V = invG_V @ (R_V * eW[:, None]).T @ D_V # functions.R:263 + # Fuzzy pilot ratio + delta vector (functions.R:264-268); R row + # nu+1 (1-based) is 0-based nu. + tau_Y = float(math.factorial(nu)) * float(beta_V[nu, 0]) + tau_T = float(math.factorial(nu)) * float(beta_V[nu, 1]) + with np.errstate(divide="ignore", invalid="ignore"): + s = np.array( + [ + float(np.float64(1.0) / np.float64(tau_T)), + float(-(np.float64(tau_Y) / np.float64(tau_T) ** 2)), + ] + ) + res_V = rdrobust_res_nn(eX, eY, nnmatch, dups[ind_V], dupsid[ind_V], t=eT) + aux = rdrobust_vce(R_V * eW[:, None], res_V, s) # functions.R:294 V_V = float((invG_V @ aux @ invG_V)[nu, nu]) # functions.R:295 v = (R_V * eW[:, None]).T @ ((eX - c) / h_V) ** (o + 1) # :296 Hp = h_V ** np.arange(o + 1, dtype=np.float64) # functions.R:297-298 BConst = float((Hp * (invG_V @ v))[nu]) # functions.R:299 if vcache is not None: - vcache[key] = (V_V, BConst) + vcache[key] = (V_V, BConst, s) # --- B-fit at o_B, bandwidth h_B (functions.R:306-348) --- w = rdrobust_kweight(x, c, h_B, kernel) ind = w > 0 @@ -338,17 +432,28 @@ def rdrobust_bw( eW = w[ind] R_B = rdrobust_vander(eX - c, o_B) invG_B = qrXXinv(R_B * np.sqrt(eW)[:, None]) - beta_B = invG_B @ (R_B * eW[:, None]).T @ eY # functions.R:326 + if t is None: + beta_B = invG_B @ (R_B * eW[:, None]).T @ eY # functions.R:326 + # functions.R:349-353 with sharp s == 1: t(s) %*% beta_B[o+2,] is + # the scalar coefficient (R row o+2 1-based = 0-based o+1). + beta_B_comb = float(beta_B[o + 1]) + else: + eT_B = t[ind] # functions.R:315-318 + D_B = np.column_stack([eY, eT_B]) + beta_B = invG_B @ (R_B * eW[:, None]).T @ D_B # functions.R:326 + assert s is not None + beta_B_comb = float(s @ beta_B[o + 1, :]) # functions.R:349 BWreg = 0.0 if scale > 0: # functions.R:328-348 - res_B = rdrobust_res_nn(eX, eY, nnmatch, dups[ind], dupsid[ind]) + if t is None: + res_B = rdrobust_res_nn(eX, eY, nnmatch, dups[ind], dupsid[ind]) + else: + res_B = rdrobust_res_nn(eX, eY, nnmatch, dups[ind], dupsid[ind], t=t[ind]) V_B = float( - (invG_B @ rdrobust_vce_sharp(R_B * eW[:, None], res_B) @ invG_B)[o + 1, o + 1] + (invG_B @ rdrobust_vce(R_B * eW[:, None], res_B, s) @ invG_B)[o + 1, o + 1] ) # functions.R:346 - R row/col o+2 is 0-based (o+1, o+1) BWreg = 3.0 * BConst**2 * V_B # functions.R:347 - # functions.R:349-353. R row o+2 (1-based) of beta_B is 0-based o+1; - # sharp s == 1 so t(s) %*% beta_B[o+2,] is the scalar coefficient. - B = float(np.sqrt(2.0 * (o + 1 - nu)) * BConst * beta_B[o + 1]) + B = float(np.sqrt(2.0 * (o + 1 - nu)) * BConst * beta_B_comb) V = float((2.0 * nu + 1.0) * h_V ** (2 * nu + 1) * V_V) R_reg = float(scale * (2.0 * (o + 1 - nu)) * BWreg) return _BwPilot(V=V, B=B, R=R_reg, rate=1.0 / (2.0 * o + 3.0)) @@ -397,7 +502,7 @@ class RdBwselectResult: diagnostics: Dict[str, float] = field(default_factory=dict) -def rdbwselect_sharp( +def rdbwselect( y: np.ndarray, x: np.ndarray, c: float = 0.0, @@ -413,19 +518,39 @@ def rdbwselect_sharp( scaleregul: float = 1.0, stdvars: bool = False, warn_masspoints: bool = True, + fuzzy: Optional[np.ndarray] = None, + sharpbw: bool = False, ) -> RdBwselectResult: - """Sharp-RD data-driven bandwidth selection, all 10 selectors - (rdbwselect.R main flow at the anchors cited inline). + """RD data-driven bandwidth selection, all 10 selectors + (rdbwselect.R main flow at the anchors cited inline; sharp and fuzzy + no-covariate paths). Always computes the full selector matrix (R's ``all=TRUE``): the ten selectors share the same six per-side pilot blocks, so the marginal cost is a handful of scalar operations. Inputs must be complete-case 1-D arrays (see module docstring for the deviation from R's silent ``complete.cases`` drop). + + Fuzzy (``fuzzy`` = observed take-up variable): bandwidths are selected + on the FUZZY RATIO objective - T is threaded into every pilot call of + all three chains so the pilot V/B constants are those of the ratio + estimator (rdbwselect.R:386-457; the port computes the mserd, msetwo, + AND msesum chains unconditionally, so T must reach all 14 + ``rdrobust_bw`` call sites, not just R's default single chain). When + ``sharpbw=True`` OR either side has zero take-up variance + (``perf_comp``, one-sided perfect compliance), T is nulled for + SELECTION ONLY and the sharp reduced-form objective on Y is used + (rdbwselect.R:334-346); estimation always remains fuzzy. Standardize + note: R's ``stdvars`` scales y and x only - the fuzzy column is never + standardized (rdbwselect.R:120-129). """ y = np.asarray(y, dtype=np.float64) x = np.asarray(x, dtype=np.float64) - for name, arr in (("y", y), ("x", x)): + arrs = [("y", y), ("x", x)] + if fuzzy is not None: + fuzzy = np.asarray(fuzzy, dtype=np.float64) + arrs.append(("fuzzy", fuzzy)) + for name, arr in arrs: # Accept true 1-D vectors or explicit (n, 1) columns; reject any # other shape rather than silently flattening a 2-D array onto # unintended (y, x) pairings. @@ -437,6 +562,19 @@ def rdbwselect_sharp( x = x.reshape(-1) if y.shape[0] != x.shape[0]: raise ValueError(f"y and x must have equal length; got {y.shape[0]} vs {x.shape[0]}.") + if fuzzy is not None: + fuzzy = fuzzy.reshape(-1) + if fuzzy.shape[0] != x.shape[0]: + raise ValueError( + f"fuzzy must have length equal to x; got {fuzzy.shape[0]} vs {x.shape[0]}." + ) + if not np.all(np.isfinite(fuzzy)): + raise ValueError( + "fuzzy must be finite and complete-case; drop or impute " + "missing values before bandwidth selection (the public " + "estimator warns-and-drops; R's complete.cases filter " + "includes the fuzzy column)." + ) if not (np.all(np.isfinite(y)) and np.all(np.isfinite(x))): raise ValueError( "y and x must be finite and complete-case; drop or impute " @@ -466,6 +604,10 @@ def rdbwselect_sharp( raise ValueError(f"bwcheck must be None or an integer >= 1; got {bwcheck!r}.") if not (np.isfinite(scaleregul) and scaleregul >= 0): raise ValueError(f"scaleregul must be a finite value >= 0; got {scaleregul!r}.") + if not isinstance(sharpbw, (bool, np.bool_)): + # Same strict-bool contract as the estimator: a truthy non-bool + # must not silently flip the bandwidth objective. + raise ValueError(f"sharpbw must be a bool; got {sharpbw!r}.") N = y.shape[0] if N < 20: # rdbwselect.R:237-239 warns and aborts (exit = 1). The estimator's @@ -481,6 +623,8 @@ def rdbwselect_sharp( order_x = np.argsort(x, kind="stable") x = x[order_x] y = y[order_x] + if fuzzy is not None: + fuzzy = fuzzy[order_x] # rdbwselect.R:112 (fuzzy = fuzzy[order_x,]) # --- Degeneracy guards BEFORE any standardization division: a constant # running variable must surface as the assumption failure it is, not as @@ -583,14 +727,32 @@ def rdbwselect_sharp( bw_min_r = float(np.abs(X_uniq_r - c)[bwcheck_r - 1]) + 1e-8 c_bw = max(c_bw, bw_min_l, bw_min_r) + # --- Fuzzy first-stage split + identification + perf_comp + # (rdbwselect.R:334-346; runs after the masspoints block, mirroring + # R's rdbwselect ordering - R's rdrobust() checks identification + # FIRST, which the estimator mirrors at fit() level). --- + T_sel_l: Optional[np.ndarray] = None + T_sel_r: Optional[np.ndarray] = None + if fuzzy is not None: + T_l_full = fuzzy[ind_l] + T_r_full = fuzzy[ind_r] + _fuzzy_identification_stop(T_l_full, T_r_full) + perf_comp = _var0(T_l_full) or _var0(T_r_full) # rdbwselect.R:343 + if not (perf_comp or sharpbw): # rdbwselect.R:344-346 null-out + T_sel_l, T_sel_r = T_l_full, T_r_full + # --- NN tie blocks (rdbwselect.R:322-327) --- dups_l, dupsid_l = compute_dups_dupsid(X_l) dups_r, dupsid_r = compute_dups_dupsid(X_r) - vcache_l: Dict[str, Tuple[float, float]] = {} - vcache_r: Dict[str, Tuple[float, float]] = {} + vcache_l: Dict[str, Tuple[float, float, Optional[np.ndarray]]] = {} + vcache_r: Dict[str, Tuple[float, float, Optional[np.ndarray]]] = {} def _bw(side: str, o: int, nu: int, o_B: int, h_B: float, scale: float) -> _BwPilot: + # Single funnel for ALL 14 pilot calls across the mserd, msetwo, + # and msesum chains: threading T here guarantees every chain's + # pilots receive the fuzzy column (R passes T_l/T_r into each + # chain's calls individually, rdbwselect.R:386-457). if side == "l": return rdrobust_bw( Y_l, @@ -607,7 +769,8 @@ def _bw(side: str, o: int, nu: int, o_B: int, h_B: float, scale: float) -> _BwPi kernel, dups_l, dupsid_l, - vcache_l, + t=T_sel_l, + vcache=vcache_l, ) return rdrobust_bw( Y_r, @@ -624,7 +787,8 @@ def _bw(side: str, o: int, nu: int, o_B: int, h_B: float, scale: float) -> _BwPi kernel, dups_r, dupsid_r, - vcache_r, + t=T_sel_r, + vcache=vcache_r, ) def _stage_bw(num, den, rate, clamp_max=None, floors=()): @@ -811,17 +975,26 @@ def _stage_bw(num, den, rate, clamp_max=None, floors=()): @dataclass -class RdFitSharpResult: - """Sharp-RD point estimates and variances (rdrobust.R estimation body). +class RdFitResult: + """RD point estimates and variances (rdrobust.R estimation body, + sharp and fuzzy no-covariate paths). - ``tau_cl`` is the conventional local-polynomial RD estimate, - ``tau_bc`` the bias-corrected estimate; ``se_cl``/``se_rb`` are the + ``tau_cl`` is the conventional RD estimate (the fuzzy ratio + ``tau_Y_cl/tau_T_cl`` on fuzzy fits), ``tau_bc`` the bias-corrected + estimate (linearized on fuzzy fits); ``se_cl``/``se_rb`` are the conventional and robust bias-corrected standard errors. rdrobust's three output rows map as Conventional = (tau_cl, se_cl), Bias-Corrected = (tau_bc, se_cl), Robust = (tau_bc, se_rb) (rdrobust.R:854-863). ``beta_p_l``/``beta_p_r`` are the per-side - order-p coefficient vectors (rdplot seam); ``bias_l``/``bias_r`` the - per-side estimated biases (rdrobust.R:629-630). + order-p outcome coefficient vectors (rdplot seam); + ``bias_l``/``bias_r`` the per-side estimated biases (sharp: + rdrobust.R:629-630; fuzzy: the LINEARIZED ``s_Y . B_F_side``, + rdrobust.R:649-652 - a different formula, not the per-component + difference). Fuzzy-only fields (None on sharp fits): the first-stage + ``tau_T_cl/tau_T_bc/se_T_cl/se_T_rb`` (rdrobust.R:637-638, 800-822) + and per-side take-up coefficient vectors ``beta_t_p_l/beta_t_p_r`` + (raw, like ``beta_p_*``; R applies ``scalepar*factorial(deriv)`` to + both - identical at the public deriv=0/scalepar=1 surface). """ tau_cl: float @@ -836,9 +1009,15 @@ class RdFitSharpResult: N_h_r: int N_b_l: int N_b_r: int + tau_T_cl: Optional[float] = None + tau_T_bc: Optional[float] = None + se_T_cl: Optional[float] = None + se_T_rb: Optional[float] = None + beta_t_p_l: Optional[np.ndarray] = None + beta_t_p_r: Optional[np.ndarray] = None -def rdrobust_fit_sharp( +def rdrobust_fit( y: np.ndarray, x: np.ndarray, c: float, @@ -852,12 +1031,13 @@ def rdrobust_fit_sharp( kernel: str = "triangular", vce: str = "nn", nnmatch: int = 3, -) -> RdFitSharpResult: - """Sharp-RD estimation at known bandwidths (rdrobust.R:533-800, sharp - no-covariate/no-cluster path with ``scalepar = 1``). + t: Optional[np.ndarray] = None, +) -> RdFitResult: + """RD estimation at known bandwidths (rdrobust.R:533-822, sharp and + fuzzy no-covariate/no-cluster paths with ``scalepar = 1``). Inputs must be complete-case 1-D arrays (same contract as - :func:`rdbwselect_sharp`); sorting, side-splitting, and NN tie blocks + :func:`rdbwselect`); sorting, side-splitting, and NN tie blocks are handled internally. Per-side bandwidths follow rdrobust's ``bws = [[h_l, b_l], [h_r, b_r]]`` layout. Steps: @@ -867,17 +1047,31 @@ def rdrobust_fit_sharp( of the p-regression through the weights. 2. Point estimates: order-p WLS per side at h (``beta_p``); the bias-corrected coefficient vector uses the ``Q_q`` score matrix - (rdrobust.R:577-578, 609-618). + (rdrobust.R:577-578, 609-618). Fuzzy (``t`` = observed take-up): + T is stacked as a second response column through the SAME fits + (rdrobust.R:581-591), the point estimate is the ratio + ``tau_Y_cl/tau_T_cl`` and the bias correction is LINEARIZED via + the delta vector ``s_Y = [1/tau_T_cl, -tau_Y_cl/tau_T_cl^2]``: + ``tau_bc = tau_cl - s_Y . B_F`` (rdrobust.R:636-657). The + identification guard (both-sides-constant T with no jump) raises + here too, covering manual-bandwidth fits that skip selection. 3. Variances: conventional sandwiches ``R_p * W_h`` with same-side NN residuals; robust sandwiches ``Q_q`` with the SAME residuals (``res_b = res_h`` for vce="nn", rdrobust.R:753-754; the h==b special branches at rdrobust.R:773-786 are cluster-only and never - taken on this path). + taken on this path). Fuzzy: the (n, 2) residual matrix is collapsed + by ``s_Y`` for the ratio variance and by ``sV_T = [0, 1]`` for the + first-stage variance (rdrobust.R:769-822); a zero first-stage jump + follows R's Inf/NaN flow-on (numpy float under ``errstate``). """ y = np.asarray(y, dtype=np.float64) x = np.asarray(x, dtype=np.float64) - for name, arr in (("y", y), ("x", x)): - # Same input contract as rdbwselect_sharp: 1-D vectors or explicit + arrs = [("y", y), ("x", x)] + if t is not None: + t = np.asarray(t, dtype=np.float64) + arrs.append(("t", t)) + for name, arr in arrs: + # Same input contract as rdbwselect: 1-D vectors or explicit # (n, 1) columns only. if not (arr.ndim == 1 or (arr.ndim == 2 and arr.shape[1] == 1)): raise ValueError( @@ -887,6 +1081,15 @@ def rdrobust_fit_sharp( x = x.reshape(-1) if y.shape[0] != x.shape[0]: raise ValueError(f"y and x must have equal length; got {y.shape[0]} vs {x.shape[0]}.") + if t is not None: + t = t.reshape(-1) + if t.shape[0] != x.shape[0]: + raise ValueError(f"t must have length equal to x; got {t.shape[0]} vs {x.shape[0]}.") + if not np.all(np.isfinite(t)): + raise ValueError( + "t must be finite and complete-case; drop or impute missing " + "values before estimation." + ) if vce != "nn": raise NotImplementedError( "Only vce='nn' is ported in v1 (rdrobust default); hc0-hc3 and " @@ -917,6 +1120,8 @@ def rdrobust_fit_sharp( order_x = np.argsort(x, kind="stable") x = x[order_x] y = y[order_x] + if t is not None: + t = t[order_x] # rdrobust.R:115 ind_l = x < c ind_r = x >= c X_l, X_r = x[ind_l], x[ind_r] @@ -926,12 +1131,20 @@ def rdrobust_fit_sharp( "All observations fall on one side of the cutoff; sharp RD " "requires data on both sides." ) + T_l = t[ind_l] if t is not None else None + T_r = t[ind_r] if t is not None else None + if T_l is not None and T_r is not None: + # rdrobust.R:164-185: the identification guard lives in the + # estimation entry point too, so manual-bandwidth fuzzy fits that + # never touch bandwidth selection still fail closed. + _fuzzy_identification_stop(T_l, T_r) dups_l, dupsid_l = compute_dups_dupsid(X_l) dups_r, dupsid_r = compute_dups_dupsid(X_r) def _side( X: np.ndarray, Y: np.ndarray, + T: Optional[np.ndarray], h: float, b: float, dups: np.ndarray, @@ -950,6 +1163,7 @@ def _side( ind = ind_h eY = Y[ind] eX = X[ind] + eT = T[ind] if T is not None else None # rdrobust.R:588-590 W_h = w_h[ind] W_b = w_b[ind] edups = dups[ind] @@ -988,42 +1202,130 @@ def _side( # 1-based = p+1 0-based). M = (R_q @ invG_q) * W_b[:, None] Q_q = R_p * W_h[:, None] - h ** (p + 1) * np.outer(M[:, p + 1], L) - # Point estimates (rdrobust.R:609-614) - beta_p = invG_p @ (R_p * W_h[:, None]).T @ eY - beta_bc = invG_p @ Q_q.T @ eY - # NN residuals shared by both variances (rdrobust.R:750-754) - res_h = rdrobust_res_nn(eX, eY, nnmatch, edups, edupsid) - # Conventional / robust meats (rdrobust.R:762-764, 789-798) - V_cl = invG_p @ rdrobust_vce_sharp(R_p * W_h[:, None], res_h) @ invG_p - V_rb = invG_p @ rdrobust_vce_sharp(Q_q, res_h) @ invG_p - return beta_p, beta_bc, V_cl, V_rb, N_h, N_b - - beta_p_l, beta_bc_l, V_cl_l, V_rb_l, N_h_l, N_b_l = _side( - X_l, Y_l, h_l, b_l, dups_l, dupsid_l, "left" + # Point estimates (rdrobust.R:609-614). Fuzzy stacks T as the + # second response column (rdrobust.R:588-591); the sharp branch + # keeps the original vector products verbatim (bit-identity). + if eT is None: + beta_p = invG_p @ (R_p * W_h[:, None]).T @ eY + beta_bc = invG_p @ Q_q.T @ eY + res_h = rdrobust_res_nn(eX, eY, nnmatch, edups, edupsid) + else: + eD = np.column_stack([eY, eT]) + beta_p = invG_p @ (R_p * W_h[:, None]).T @ eD + beta_bc = invG_p @ Q_q.T @ eD + # NN residual matrix, T sharing Y's neighbor sets + # (rdrobust.R:750-754; functions.R:171-174). + res_h = rdrobust_res_nn(eX, eY, nnmatch, edups, edupsid, t=eT) + return beta_p, beta_bc, invG_p, R_p * W_h[:, None], Q_q, res_h, N_h, N_b + + beta_p_l, beta_bc_l, invG_p_l, RX_cl_l, Q_q_l, res_l, N_h_l, N_b_l = _side( + X_l, Y_l, T_l, h_l, b_l, dups_l, dupsid_l, "left" ) - beta_p_r, beta_bc_r, V_cl_r, V_rb_r, N_h_r, N_b_r = _side( - X_r, Y_r, h_r, b_r, dups_r, dupsid_r, "right" + beta_p_r, beta_bc_r, invG_p_r, RX_cl_r, Q_q_r, res_r, N_h_r, N_b_r = _side( + X_r, Y_r, T_r, h_r, b_r, dups_r, dupsid_r, "right" ) # factorial(deriv) scaling per rdrobust.R:621-622 (deriv=0 -> 1). fact = float(math.factorial(deriv)) - tau_cl = fact * float(beta_p_r[deriv] - beta_p_l[deriv]) - tau_bc = fact * float(beta_bc_r[deriv] - beta_bc_l[deriv]) - bias_l = fact * float(beta_p_l[deriv]) - fact * float(beta_bc_l[deriv]) - bias_r = fact * float(beta_p_r[deriv]) - fact * float(beta_bc_r[deriv]) - V_tau_cl = fact**2 * float((V_cl_l + V_cl_r)[deriv, deriv]) - V_tau_rb = fact**2 * float((V_rb_l + V_rb_r)[deriv, deriv]) - return RdFitSharpResult( + + def _v(invG_p, RX, res, s): + # Conventional / robust meats (rdrobust.R:762-764, 789-798); the + # fuzzy s collapses the residual matrix (functions.R:379-385). + return invG_p @ rdrobust_vce(RX, res, s) @ invG_p + + if t is None: + tau_cl = fact * float(beta_p_r[deriv] - beta_p_l[deriv]) + tau_bc = fact * float(beta_bc_r[deriv] - beta_bc_l[deriv]) + bias_l = fact * float(beta_p_l[deriv]) - fact * float(beta_bc_l[deriv]) + bias_r = fact * float(beta_p_r[deriv]) - fact * float(beta_bc_r[deriv]) + V_cl_l = _v(invG_p_l, RX_cl_l, res_l, None) + V_cl_r = _v(invG_p_r, RX_cl_r, res_r, None) + V_rb_l = _v(invG_p_l, Q_q_l, res_l, None) + V_rb_r = _v(invG_p_r, Q_q_r, res_r, None) + V_tau_cl = fact**2 * float((V_cl_l + V_cl_r)[deriv, deriv]) + V_tau_rb = fact**2 * float((V_rb_l + V_rb_r)[deriv, deriv]) + return RdFitResult( + tau_cl=tau_cl, + tau_bc=tau_bc, + se_cl=float(np.sqrt(V_tau_cl)), + se_rb=float(np.sqrt(V_tau_rb)), + bias_l=bias_l, + bias_r=bias_r, + beta_p_l=beta_p_l, + beta_p_r=beta_p_r, + N_h_l=N_h_l, + N_h_r=N_h_r, + N_b_l=N_b_l, + N_b_r=N_b_r, + ) + + # ---- Fuzzy assembly (rdrobust.R:636-657, 769-822; scalepar = 1) ---- + tau_Y_cl = fact * float(beta_p_r[deriv, 0] - beta_p_l[deriv, 0]) + tau_Y_bc = fact * float(beta_bc_r[deriv, 0] - beta_bc_l[deriv, 0]) + tau_T_cl = fact * float(beta_p_r[deriv, 1] - beta_p_l[deriv, 1]) + tau_T_bc = fact * float(beta_bc_r[deriv, 1] - beta_bc_l[deriv, 1]) + with np.errstate(divide="ignore", invalid="ignore"): + # R flows Inf/NaN through a zero first-stage jump (no guard at + # rdrobust.R:639-642); numpy-float division mirrors that instead + # of raising ZeroDivisionError. Non-finite results NaN-gate the + # downstream inference (estimator contract). + tau_cl = float(np.float64(tau_Y_cl) / np.float64(tau_T_cl)) + s_Y = np.array( + [ + float(np.float64(1.0) / np.float64(tau_T_cl)), + float(-(np.float64(tau_Y_cl) / np.float64(tau_T_cl) ** 2)), + ] + ) + B_F = np.array([tau_Y_cl - tau_Y_bc, tau_T_cl - tau_T_bc]) # rdrobust.R:641 + tau_bc = float(tau_cl - s_Y @ B_F) # rdrobust.R:642 (linearized) + sV_T = np.array([0.0, 1.0]) # rdrobust.R:643 + # Fuzzy per-side biases are the LINEARIZED s_Y . B_F_side + # (rdrobust.R:645-652), not the sharp per-component differences. + B_F_l = np.array( + [ + fact * float(beta_p_l[deriv, 0] - beta_bc_l[deriv, 0]), + fact * float(beta_p_l[deriv, 1] - beta_bc_l[deriv, 1]), + ] + ) + B_F_r = np.array( + [ + fact * float(beta_p_r[deriv, 0] - beta_bc_r[deriv, 0]), + fact * float(beta_p_r[deriv, 1] - beta_bc_r[deriv, 1]), + ] + ) + bias_l = float(s_Y @ B_F_l) + bias_r = float(s_Y @ B_F_r) + # Ratio variance with s_Y; first-stage variance with sV_T + # (rdrobust.R:769-798 and 800-822, no-cluster branches). + V_tau_cl = fact**2 * float( + (_v(invG_p_l, RX_cl_l, res_l, s_Y) + _v(invG_p_r, RX_cl_r, res_r, s_Y))[deriv, deriv] + ) + V_tau_rb = fact**2 * float( + (_v(invG_p_l, Q_q_l, res_l, s_Y) + _v(invG_p_r, Q_q_r, res_r, s_Y))[deriv, deriv] + ) + V_T_cl = fact**2 * float( + (_v(invG_p_l, RX_cl_l, res_l, sV_T) + _v(invG_p_r, RX_cl_r, res_r, sV_T))[deriv, deriv] + ) + V_T_rb = fact**2 * float( + (_v(invG_p_l, Q_q_l, res_l, sV_T) + _v(invG_p_r, Q_q_r, res_r, sV_T))[deriv, deriv] + ) + return RdFitResult( tau_cl=tau_cl, tau_bc=tau_bc, se_cl=float(np.sqrt(V_tau_cl)), se_rb=float(np.sqrt(V_tau_rb)), bias_l=bias_l, bias_r=bias_r, - beta_p_l=beta_p_l, - beta_p_r=beta_p_r, + beta_p_l=beta_p_l[:, 0], + beta_p_r=beta_p_r[:, 0], N_h_l=N_h_l, N_h_r=N_h_r, N_b_l=N_b_l, N_b_r=N_b_r, + tau_T_cl=tau_T_cl, + tau_T_bc=tau_T_bc, + se_T_cl=float(np.sqrt(V_T_cl)), + se_T_rb=float(np.sqrt(V_T_rb)), + beta_t_p_l=beta_p_l[:, 1], + beta_t_p_r=beta_p_r[:, 1], ) diff --git a/diff_diff/guides/llms-autonomous.txt b/diff_diff/guides/llms-autonomous.txt index 52359fa0..5fc88885 100644 --- a/diff_diff/guides/llms-autonomous.txt +++ b/diff_diff/guides/llms-autonomous.txt @@ -355,7 +355,7 @@ supported / out of scope; `warn` supported but with documented caveats; | `StaggeredTripleDifference` | ✓ | ✓ | ✗ | ✓ | ✗ | ✓ | ✗ | ✗ | ✓ | | `ContinuousDiD` | ✗ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✓ | | `HeterogeneousAdoptionDiD` | ✗ | partial | partial | ✗ | ✗ | ✗ | ✗ | ✓ | warn | -| `RegressionDiscontinuity` | ✗ (cross-sectional; treatment = running >= cutoff) | ✗ | ✗ | ✗ | ✗ | ✗ (follow-up) | ✗ | ✗ | ✗ (follow-up) | +| `RegressionDiscontinuity` | ✗ (cross-sectional; sharp: treatment = running >= cutoff; fuzzy: observed take-up via `treatment_col=`) | ✗ | ✗ | ✗ | ✗ | ✗ (follow-up) | ✗ | ✗ | ✗ (follow-up) | **Footnotes.** - `TwoWayFixedEffects` + staggered: fits but mixes positive and negative @@ -1297,13 +1297,15 @@ This guide does **not**: estimator-native diagnostics. Post-fit validation is mandatory, not optional, and belongs in the final write-up. - Cover methods outside diff-diff's estimator suite (e.g., instrumental - variables, fuzzy regression discontinuity, synthetic control for a - single treated unit). When those apply, point the user at dedicated - libraries. SHARP regression discontinuity IS in scope as of this - release: route running-variable/threshold designs to - `RegressionDiscontinuity` (alias `RDD`; sharp designs only - fuzzy RD, - covariate adjustment, and cluster-robust RD variance are documented - follow-ups, so point users needing those at R rdrobust). + variables, regression KINK designs, synthetic control for a single + treated unit). When those apply, point the user at dedicated + libraries. Regression discontinuity IS in scope - BOTH sharp and + fuzzy: route running-variable/threshold designs to + `RegressionDiscontinuity` (alias `RDD`); imperfect compliance at the + threshold is the fuzzy design (`fit(..., treatment_col=...)`, local + Wald ratio with a first-stage block). Covariate adjustment, + cluster-robust RD variance, and weak-IV-robust fuzzy inference are + documented follow-ups, so point users needing those at R rdrobust. **If in doubt, consult the primary references in §8 and use `get_llm_guide("practitioner")` for the Baker et al. workflow.** diff --git a/diff_diff/guides/llms-full.txt b/diff_diff/guides/llms-full.txt index 235ee2ff..7743bd85 100644 --- a/diff_diff/guides/llms-full.txt +++ b/diff_diff/guides/llms-full.txt @@ -826,7 +826,7 @@ es = est.fit(data_mp, outcome_col='y', unit_col='unit', ### RegressionDiscontinuity -Sharp regression discontinuity estimator (Calonico, Cattaneo & Titiunik 2014), parity-targeting R rdrobust 4.0.0. Treatment is assigned by a known threshold of an observed running variable (`running >= cutoff`; units exactly at the cutoff are treated). Point estimation via kernel-weighted local polynomials on each side; data-driven MSE/CER-optimal bandwidths (all 10 rdrobust selectors); robust bias-corrected inference. Cross-sectional - no panel/time dimension and no treatment column (sharp design derives treatment from the running variable). +Regression discontinuity estimator - sharp and fuzzy (Calonico, Cattaneo & Titiunik 2014), parity-targeting R rdrobust 4.0.0. SHARP (default): treatment is assigned by a known threshold of an observed running variable (`running >= cutoff`; units exactly at the cutoff are treated); no treatment column. FUZZY: pass the OBSERVED take-up column via `fit(..., treatment_col=...)` (R's `fuzzy=`) - the estimand becomes the local Wald ratio (complier LATE at the cutoff under monotonicity) with a linearized bias correction, and the results gain a full `first_stage*` three-row block. Point estimation via kernel-weighted local polynomials on each side; data-driven MSE/CER-optimal bandwidths (all 10 rdrobust selectors; fuzzy selects on the ratio objective by default, with a sharp-on-Y switch under one-sided perfect compliance or `sharpbw=True`); robust bias-corrected inference. Cross-sectional - no panel/time dimension. ```python RegressionDiscontinuity( @@ -844,6 +844,7 @@ RegressionDiscontinuity( bwcheck: int | None = None, # Force >= this many unique support points into the window bwrestrict: bool = True, # Clamp bandwidths to the observed running-variable range scaleregul: float = 1.0, # IK-style regularization scale (0 removes) + sharpbw: bool = False, # Fuzzy only: select bandwidths on the sharp reduced form (R's sharpbw); auto under one-sided perfect compliance alpha: float = 0.05, # rdrobust level = 100*(1-alpha) ) ``` @@ -857,6 +858,7 @@ rd.fit( data: pd.DataFrame, outcome_col: str, running_col: str, + treatment_col: str | None = None, # None = sharp; a column name = fuzzy (observed take-up; any numeric, typically binary) ) -> RegressionDiscontinuityResults ``` @@ -870,15 +872,23 @@ results.att_conventional # rdrobust's printed headline coefficient (conventiona results.conf_int # robust bias-corrected CI results.h_left, results.b_left # selected bandwidths print(results.summary()) # three-row Conventional / Bias-Corrected / Robust table, as in rdrobust + +fuzzy = rd.fit(df, "y", "score", treatment_col="takeup") # fuzzy RD +fuzzy.att # complier LATE at the cutoff (linearized bias-corrected ratio, robust row) +fuzzy.first_stage # take-up jump (bias-corrected; full three-row first_stage* mirror available) +fuzzy.estimand # "fuzzy (LATE for compliers at the cutoff)" (binary take-up) / "fuzzy (local Wald ratio at the cutoff; non-binary take-up)" / "sharp (ATE at the cutoff)" ``` Canonical fields are ONE coherent row (the robust row): att = bias-corrected estimate, se = robust SE. rdrobust prints the conventional estimate as its headline - that is att_conventional here, with a full inference row of its own. -Sharp designs only in this release: fuzzy RD, covariates, cluster-robust -variance, weights, and kink estimands are documented follow-ups; missing -rows are dropped WITH a warning (R drops silently); N < 20 falls back to -full-range bandwidths exactly as rdrobust does (overriding manual h). +Fuzzy fits warn when the first-stage robust CI contains zero (weak +identification; R is silent) and raise R's exact error when the take-up +variable has no variation and no jump. Covariates, cluster-robust variance, +weights, kink estimands, and weak-IV-robust fuzzy inference are documented +follow-ups; missing rows are dropped WITH a warning (R drops silently); +N < 20 falls back to full-range bandwidths exactly as rdrobust does +(overriding manual h). ### StackedDiD diff --git a/diff_diff/guides/llms.txt b/diff_diff/guides/llms.txt index c34a2b52..fd3ac24a 100644 --- a/diff_diff/guides/llms.txt +++ b/diff_diff/guides/llms.txt @@ -64,7 +64,7 @@ Full practitioner guide: call `diff_diff.get_llm_guide("practitioner")` - [TripleDifference](https://diff-diff.readthedocs.io/en/stable/api/triple_diff.html): Triple difference (DDD) estimator for designs requiring two criteria for treatment eligibility - [ContinuousDiD](https://diff-diff.readthedocs.io/en/stable/api/continuous_did.html): Callaway, Goodman-Bacon & Sant'Anna (2024) continuous treatment DiD with dose-response curves - [HeterogeneousAdoptionDiD](https://diff-diff.readthedocs.io/en/stable/api/had.html): de Chaisemartin, Ciccia, D'Haultfœuille & Knau (2026) for designs where **no unit remains untreated**; local-linear estimator at the dose support boundary returning Weighted Average Slope (WAS) on Design 1' (`d̲=0` / QUG) or `WAS_{d̲}` on Design 1 (`d̲>0`, continuous-near-d̲ or mass-point), with multi-period event-study extension (last-treatment cohort, pointwise CIs). **Panel-only** in this release (repeated cross-sections rejected by the validator). Alias `HAD`. -- [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html): Calonico, Cattaneo & Titiunik (2014) sharp regression discontinuity with robust bias-corrected inference, parity-targeting R rdrobust 4.0.0 (all 10 data-driven bandwidth selectors, mass-point handling, three-row conventional/bias-corrected/robust output; canonical `att` = the bias-corrected estimate with a coherent robust CI - rdrobust's printed headline is `att_conventional`). **Sharp designs only** in this release (fuzzy/covariates/cluster documented follow-ups). Alias `RDD`. +- [RegressionDiscontinuity](https://diff-diff.readthedocs.io/en/stable/api/regression_discontinuity.html): Calonico, Cattaneo & Titiunik (2014) sharp AND fuzzy regression discontinuity with robust bias-corrected inference, parity-targeting R rdrobust 4.0.0 (all 10 data-driven bandwidth selectors, mass-point handling, three-row conventional/bias-corrected/robust output; canonical `att` = the bias-corrected estimate with a coherent robust CI - rdrobust's printed headline is `att_conventional`). Fuzzy via `fit(..., treatment_col=...)`: local Wald ratio (complier LATE for binary take-up under monotonicity; ratio-of-jumps otherwise - the `estimand` field says which), first-stage `first_stage*` block, weak-first-stage warning. Covariates/cluster are documented follow-ups. Alias `RDD`. - [StackedDiD](https://diff-diff.readthedocs.io/en/stable/api/stacked_did.html): Wing, Freedman & Hollingsworth (2024) stacked DiD with Q-weights and sub-experiments; optional covariate balancing (`balance="entropy"`, Ustyuzhanin 2026) - [EfficientDiD](https://diff-diff.readthedocs.io/en/stable/api/efficient_did.html): Chen, Sant'Anna & Xie (2025) efficient DiD with optimal weighting for tighter SEs - [TROP](https://diff-diff.readthedocs.io/en/stable/api/trop.html): Triply Robust Panel estimator (Athey et al. 2025) with nuclear norm factor adjustment (absorbing by default; `non_absorbing=True` for on/off treatment, method='local') diff --git a/diff_diff/rdd.py b/diff_diff/rdd.py index 1f9a85ff..efd8d0e1 100644 --- a/diff_diff/rdd.py +++ b/diff_diff/rdd.py @@ -1,46 +1,61 @@ """ -Sharp regression discontinuity design (RDD) estimation with robust -bias-corrected inference, parity-targeting R ``rdrobust`` 4.0.0. - -Implements the local-polynomial sharp-RD estimator of Calonico, Cattaneo & -Titiunik (2014): treatment is assigned by ``running >= cutoff``; the effect -is the jump in the conditional expectation of the outcome at the cutoff, -estimated by kernel-weighted polynomial regressions on each side with -data-driven MSE/CER-optimal bandwidths, and reported with robust -bias-corrected (RBC) inference. +Regression discontinuity design (RDD) estimation - sharp and fuzzy - with +robust bias-corrected inference, parity-targeting R ``rdrobust`` 4.0.0. + +Implements the local-polynomial RD estimators of Calonico, Cattaneo & +Titiunik (2014). SHARP (default): treatment is assigned by +``running >= cutoff``; the effect is the jump in the conditional +expectation of the outcome at the cutoff. FUZZY (pass +``fit(..., treatment_col=...)`` with the OBSERVED take-up column): +crossing the cutoff shifts take-up rather than determining it, and the +estimand is the local Wald ratio - the outcome jump divided by the +take-up jump - which under monotonicity is the LATE for compliers at the +cutoff. Both designs use kernel-weighted polynomial regressions on each +side with data-driven MSE/CER-optimal bandwidths and robust +bias-corrected (RBC) inference; the fuzzy bias correction is the +linearization of the ratio (not per-component), matching CCT 2014 +Section 3.2 and rdrobust exactly. Canonical inference binding --------------------------- ``RegressionDiscontinuityResults`` binds the library-canonical fields to ONE internally coherent inference row - the ROBUST row of rdrobust's output: -``att`` is the bias-corrected point estimate ``tau_bc``, ``se`` its robust -standard error, and ``t_stat``/``p_value``/``conf_int`` are computed from -that same pair, so the library-wide identities hold (``t_stat == att/se``, -``conf_int`` centered on ``att``). This deliberately differs from rdrobust's +``att`` is the bias-corrected point estimate ``tau_bc`` (the linearized +bias-corrected RATIO on fuzzy fits), ``se`` its robust standard error, and +``t_stat``/``p_value``/``conf_int`` are computed from that same pair, so +the library-wide identities hold (``t_stat == att/se``, ``conf_int`` +centered on ``att``). The ``estimand`` results field names what ``att`` +measures for the fit at hand. This deliberately differs from rdrobust's PRINTED headline, which reports the conventional estimate ``tau_cl`` in the coefficient column while taking inference from the robust row; ``tau_cl`` is first-class here as ``att_conventional`` (with its own full inference row), -and ``summary()`` prints the familiar three-row rdrobust table. +and ``summary()`` prints the familiar three-row rdrobust table. Fuzzy fits +additionally expose the first stage (take-up jump) as a full three-row +mirror (``first_stage*`` fields) and print it above the treatment effects, +as R does. rdrobust equivalents -------------------- -===================== ========================================== -diff-diff R rdrobust -===================== ========================================== -``cutoff`` ``c`` -``vcov_type`` ``vce`` -``alpha`` ``1 - level/100`` -``h``, ``b``, ``rho`` ``h``, ``b``, ``rho`` (same semantics) -``p``, ``q`` ``p``, ``q`` -``bwselect`` ``bwselect`` (same 10-option menu) -``kernel`` ``kernel`` (accepts "tri"/"epa"/"uni" too) -``masspoints`` ``masspoints`` ("adjust"/"check"/"off") -``nnmatch`` ``nnmatch`` -===================== ========================================== - -Not in v1 (documented seams, see REGISTRY.md): fuzzy designs, covariate -adjustment, cluster-robust variance, weights, ``deriv``/kink estimands, -``scalepar``, ``stdvars``, hc0-hc3 variance modes. +======================= ========================================== +diff-diff R rdrobust +======================= ========================================== +``cutoff`` ``c`` +``vcov_type`` ``vce`` +``alpha`` ``1 - level/100`` +``h``, ``b``, ``rho`` ``h``, ``b``, ``rho`` (same semantics) +``p``, ``q`` ``p``, ``q`` +``bwselect`` ``bwselect`` (same 10-option menu) +``kernel`` ``kernel`` (accepts "tri"/"epa"/"uni" too) +``masspoints`` ``masspoints`` ("adjust"/"check"/"off") +``nnmatch`` ``nnmatch`` +``treatment_col`` (fit) ``fuzzy`` (observed take-up variable) +``sharpbw`` ``sharpbw`` (same default and semantics) +======================= ========================================== + +Not in v1 (documented seams, see REGISTRY.md): covariate adjustment, +cluster-robust variance, weights, ``deriv``/kink estimands, ``scalepar``, +``stdvars``, hc0-hc3 variance modes, weak-IV-robust fuzzy inference +(Feir-Lemieux-Marmer). References ---------- @@ -66,9 +81,10 @@ from diff_diff._rdrobust_port import ( BWSELECT_OPTIONS, + _fuzzy_identification_stop, _normalize_kernel, - rdbwselect_sharp, - rdrobust_fit_sharp, + rdbwselect, + rdrobust_fit, ) from diff_diff.utils import safe_inference @@ -86,7 +102,9 @@ def _json_safe(value: Any) -> Any: @dataclass class RegressionDiscontinuityResults: - """Results of a sharp regression discontinuity fit. + """Results of a regression discontinuity fit (sharp or fuzzy; the + ``estimand`` field names which one, and ``first_stage*`` fields are + populated on fuzzy fits only). Canonical inference fields (``att``, ``se``, ``t_stat``, ``p_value``, ``conf_int``) all describe the ROBUST bias-corrected row: ``att`` is the @@ -167,11 +185,46 @@ class RegressionDiscontinuityResults: h_input: Optional[float] b_input: Optional[float] rho_input: Optional[float] - - # Per-side order-p coefficient vectors (rdplot seam); always populated - # by fit(), so typed non-Optional despite the dataclass default. + # Design echoes: ``estimand`` names what ``att`` measures for THIS fit + # - "sharp (ATE at the cutoff)"; "fuzzy (LATE for compliers at the + # cutoff)" for BINARY take-up; or "fuzzy (local Wald ratio at the + # cutoff; non-binary take-up)" when the take-up column is not {0, 1} + # (the complier-LATE reading does not apply to dose take-up). + # ``treatment_col`` is the fit-time take-up column name + # (None on sharp fits; no ``_input`` suffix - that convention is + # reserved for constructor arguments); ``sharpbw`` echoes the + # constructor flag. + estimand: str + sharpbw: bool + treatment_col: Optional[str] + + # First-stage (take-up jump) three-row mirror - fuzzy fits only, all + # None on sharp fits. Same binding rule as the main estimate: the + # unsuffixed quintet is the coherent ROBUST row (first_stage = the + # bias-corrected first-stage estimate tau_T_bc, first_stage_se = its + # robust SE); the conventional row and the bias-corrected middle-row + # inference triple mirror the main fields' suffix scheme. + first_stage: Optional[float] = None + first_stage_se: Optional[float] = None + first_stage_t_stat: Optional[float] = None + first_stage_p_value: Optional[float] = None + first_stage_conf_int: Optional[Tuple[float, float]] = None + first_stage_conventional: Optional[float] = None + first_stage_se_conventional: Optional[float] = None + first_stage_t_stat_conventional: Optional[float] = None + first_stage_p_value_conventional: Optional[float] = None + first_stage_conf_int_conventional: Optional[Tuple[float, float]] = None + first_stage_t_stat_bias_corrected: Optional[float] = None + first_stage_p_value_bias_corrected: Optional[float] = None + first_stage_conf_int_bias_corrected: Optional[Tuple[float, float]] = None + + # Per-side order-p coefficient vectors (rdplot seam); the outcome pair + # is always populated by fit(), so typed non-Optional despite the + # dataclass default; the take-up pair is fuzzy-only. beta_p_left: np.ndarray = field(repr=False, default=None) beta_p_right: np.ndarray = field(repr=False, default=None) + beta_t_p_left: Optional[np.ndarray] = field(repr=False, default=None) + beta_t_p_right: Optional[np.ndarray] = field(repr=False, default=None) def summary(self) -> str: """Human-readable summary with the three-row rdrobust table.""" @@ -179,9 +232,11 @@ def summary(self) -> str: conf_level = 100 * (1 - self.alpha) lines = [] lines.append("=" * width) - lines.append("Sharp Regression Discontinuity (rdrobust parity)".center(width)) + design = "Fuzzy" if self.first_stage is not None else "Sharp" + lines.append(f"{design} Regression Discontinuity (rdrobust parity)".center(width)) lines.append("=" * width) lines.append(f"Cutoff: {self.cutoff:g}") + lines.append(f"Estimand: {self.estimand}") lines.append(f"Kernel: {self.kernel:<14} Bandwidth selector: {self.bwselect}") lines.append( f"Order (p, q): ({self.p}, {self.q}) VCE: {self.vcov_type} " @@ -201,6 +256,47 @@ def summary(self) -> str: f"{'Method':<16}{'Coef.':>11}{'Std. Err.':>11}{'z':>9}" f"{'P>|z|':>9}{'[' + f'{conf_level:g}% Conf. Int.]':>16}" ) + if self.first_stage is not None: + # Fuzzy: R prints a first-stage block above the treatment + # effects (print.summary.rdrobust); same three-row structure. + lines.append("First-stage estimates (treatment take-up jump)".center(width)) + lines.append(header) + lines.append("-" * width) + fs_rows = [ + ( + "Conventional", + self.first_stage_conventional, + self.first_stage_se_conventional, + self.first_stage_t_stat_conventional, + self.first_stage_p_value_conventional, + self.first_stage_conf_int_conventional, + ), + ( + "Bias-Corrected", + self.first_stage, + self.first_stage_se_conventional, + self.first_stage_t_stat_bias_corrected, + self.first_stage_p_value_bias_corrected, + self.first_stage_conf_int_bias_corrected, + ), + ( + "Robust", + self.first_stage, + self.first_stage_se, + self.first_stage_t_stat, + self.first_stage_p_value, + self.first_stage_conf_int, + ), + ] + for name, coef, se, z, pv, ci in fs_rows: + assert coef is not None and se is not None and ci is not None + assert z is not None and pv is not None + lines.append( + f"{name:<16}{coef:>11.4f}{se:>11.4f}{z:>9.3f}{pv:>9.3f}" + f" [{ci[0]:>7.4f}, {ci[1]:>7.4f}]" + ) + lines.append("-" * width) + lines.append("Treatment effect estimates".center(width)) lines.append(header) lines.append("-" * width) rows = [ @@ -293,7 +389,28 @@ def to_dict(self) -> Dict[str, Any]: "h_input": self.h_input, "b_input": self.b_input, "rho_input": self.rho_input, + "estimand": self.estimand, + "sharpbw": self.sharpbw, + "treatment_col": self.treatment_col, + "first_stage": self.first_stage, + "first_stage_se": self.first_stage_se, + "first_stage_t_stat": self.first_stage_t_stat, + "first_stage_p_value": self.first_stage_p_value, + "first_stage_conventional": self.first_stage_conventional, + "first_stage_se_conventional": self.first_stage_se_conventional, + "first_stage_t_stat_conventional": self.first_stage_t_stat_conventional, + "first_stage_p_value_conventional": self.first_stage_p_value_conventional, + "first_stage_t_stat_bias_corrected": self.first_stage_t_stat_bias_corrected, + "first_stage_p_value_bias_corrected": self.first_stage_p_value_bias_corrected, } + # First-stage CIs are None on sharp fits - guard the tuple splits. + for key, ci in ( + ("first_stage_conf_int", self.first_stage_conf_int), + ("first_stage_conf_int_conventional", self.first_stage_conf_int_conventional), + ("first_stage_conf_int_bias_corrected", self.first_stage_conf_int_bias_corrected), + ): + out[f"{key}_lower"] = None if ci is None else ci[0] + out[f"{key}_upper"] = None if ci is None else ci[1] return {k: _json_safe(v) for k, v in out.items()} def to_dataframe(self) -> pd.DataFrame: @@ -301,14 +418,19 @@ def to_dataframe(self) -> pd.DataFrame: class RegressionDiscontinuity: - """Sharp regression discontinuity estimator (rdrobust 4.0.0 parity). - - Treatment is defined by the running variable crossing a known cutoff - (``running >= cutoff`` treated, matching rdrobust: units exactly at the - cutoff are treated). Point estimation uses kernel-weighted local - polynomials of order ``p`` on each side; inference is robust - bias-corrected per Calonico, Cattaneo & Titiunik (2014). Defaults - reproduce ``rdrobust(y, x)``: ``p=1``, ``q=2``, triangular kernel, + """Regression discontinuity estimator, sharp and fuzzy (rdrobust + 4.0.0 parity). + + SHARP (default): treatment is defined by the running variable crossing + a known cutoff (``running >= cutoff`` treated, matching rdrobust: + units exactly at the cutoff are treated). FUZZY: pass the observed + take-up column via ``fit(..., treatment_col=...)`` - the estimand + becomes the local Wald ratio (complier LATE at the cutoff under + monotonicity) and the results gain a first-stage block. Point + estimation uses kernel-weighted local polynomials of order ``p`` on + each side; inference is robust bias-corrected per Calonico, Cattaneo & + Titiunik (2014). Defaults reproduce ``rdrobust(y, x)`` / + ``rdrobust(y, x, fuzzy=t)``: ``p=1``, ``q=2``, triangular kernel, ``bwselect="mserd"``, nearest-neighbor variance with 3 matches, ``masspoints="adjust"``. @@ -356,6 +478,15 @@ class RegressionDiscontinuity: scaleregul : float, default 1.0 Scale of the IK-style regularization in bandwidth selection (0 removes it). + sharpbw : bool, default False + Fuzzy fits only (``fit(..., treatment_col=...)``): when True, + bandwidths are selected for the SHARP reduced-form estimator on + the outcome (rdrobust's "approach 1") instead of the default + fuzzy-ratio objective. Automatically in effect - regardless of + this flag - under one-sided perfect compliance (zero take-up + variance on either side), exactly as in R. On sharp fits the flag + has no effect and a warning is emitted (R ignores it silently - + documented deviation). alpha : float, default 0.05 Significance level (rdrobust ``level = 100*(1-alpha)``). @@ -364,6 +495,8 @@ class RegressionDiscontinuity: >>> rd = RegressionDiscontinuity(cutoff=0.0) >>> results = rd.fit(df, outcome_col="y", running_col="x") >>> results.att, results.conf_int # robust bias-corrected inference + >>> fuzzy = rd.fit(df, "y", "x", treatment_col="takeup") # fuzzy RD + >>> fuzzy.att, fuzzy.first_stage # complier LATE + take-up jump """ def __init__( @@ -382,6 +515,7 @@ def __init__( bwcheck: Optional[int] = None, bwrestrict: bool = True, scaleregul: float = 1.0, + sharpbw: bool = False, alpha: float = 0.05, ): self.cutoff = cutoff @@ -398,6 +532,7 @@ def __init__( self.bwcheck = bwcheck self.bwrestrict = bwrestrict self.scaleregul = scaleregul + self.sharpbw = sharpbw self.alpha = alpha self._validate_constructor_args() @@ -455,6 +590,8 @@ def _validate_constructor_args(self) -> None: # No silent truthiness: a string like "False" must not coerce # to bandwidth-restriction ON. raise ValueError(f"bwrestrict must be a bool; got {self.bwrestrict!r}.") + if not isinstance(self.sharpbw, (bool, np.bool_)): + raise ValueError(f"sharpbw must be a bool; got {self.sharpbw!r}.") if not ( self._is_real_scalar(self.scaleregul) and np.isfinite(self.scaleregul) @@ -482,6 +619,7 @@ def get_params(self, deep: bool = True) -> Dict[str, Any]: "bwcheck": self.bwcheck, "bwrestrict": self.bwrestrict, "scaleregul": self.scaleregul, + "sharpbw": self.sharpbw, "alpha": self.alpha, } @@ -506,34 +644,73 @@ def fit( data: pd.DataFrame, outcome_col: str, running_col: str, + treatment_col: Optional[str] = None, ) -> RegressionDiscontinuityResults: - """Estimate the sharp RD effect at the cutoff. + """Estimate the RD effect at the cutoff (sharp or fuzzy). Parameters ---------- data : pd.DataFrame - Cross-sectional data. Treatment is derived as - ``running >= cutoff`` (no treatment column - sharp design). + Cross-sectional data. outcome_col, running_col : str Column names of the outcome and the running variable. + treatment_col : str or None, default None + ``None`` (sharp design): treatment is derived as + ``running >= cutoff``; no treatment column is needed. A column + name activates the FUZZY design: the column holds the OBSERVED + treatment take-up (typically binary, any numeric accepted, + matching R's ``fuzzy=``), the estimand becomes the local Wald + ratio, and the results gain the ``first_stage*`` block. The + ``estimand`` label is data-dependent: for BINARY take-up it + reads "fuzzy (LATE for compliers at the cutoff)" (the + monotonicity-based complier reading); for non-binary (dose) + take-up it reads "fuzzy (local Wald ratio at the cutoff; + non-binary take-up)" - the complier-LATE interpretation does + not apply there. A take-up column that is deterministic in + the running variable reproduces the sharp fit exactly + (first stage == 1). """ - for col in (outcome_col, running_col): + cols = [outcome_col, running_col] + if treatment_col is not None: + cols.append(treatment_col) + for col in cols: if col not in data.columns: raise ValueError(f"Column {col!r} not found in data.") + fuzzy_fit = treatment_col is not None + if self.sharpbw and not fuzzy_fit: + # Deviation from R, which silently ignores sharpbw on sharp + # fits (no-silent-failures policy; same pattern as b-without-h). + warnings.warn( + "sharpbw has no effect without treatment_col (sharp design) " "and is ignored.", + UserWarning, + stacklevel=2, + ) y_raw = np.asarray(pd.to_numeric(data[outcome_col], errors="coerce"), dtype=np.float64) x_raw = np.asarray(pd.to_numeric(data[running_col], errors="coerce"), dtype=np.float64) ok = np.isfinite(y_raw) & np.isfinite(x_raw) + t_raw: Optional[np.ndarray] = None + if fuzzy_fit: + # R's complete.cases filter includes the fuzzy column + # (rdrobust.R:86-89) - the joint drop must too. + t_raw = np.asarray( + pd.to_numeric(data[treatment_col], errors="coerce"), dtype=np.float64 + ) + ok = ok & np.isfinite(t_raw) n_dropped = int(y_raw.shape[0] - np.sum(ok)) if n_dropped > 0: # Deviation from R (which drops silently via complete.cases): + dropped_cols = f"{outcome_col!r}/{running_col!r}" + if fuzzy_fit: + dropped_cols += f"/{treatment_col!r}" warnings.warn( f"Dropping {n_dropped} row(s) with missing or non-numeric " - f"values in {outcome_col!r}/{running_col!r}.", + f"values in {dropped_cols}.", UserWarning, stacklevel=2, ) y = y_raw[ok] x = x_raw[ok] + t = t_raw[ok] if t_raw is not None else None N = int(y.shape[0]) if N == 0: raise ValueError("No complete-case observations to fit on.") @@ -547,6 +724,14 @@ def fit( q = int(self.q) if self.q is not None else p + 1 kernel = _normalize_kernel(self.kernel) + # --- Fuzzy identification check (rdrobust.R:164-185) --- + # Hoisted to run immediately after the NaN drop and BEFORE + # mass-point detection, matching R's rdrobust ordering exactly + # (live-verified: R raises this with NO mass-point warning on + # degenerate fuzzy + tied data). The port re-checks defensively. + if t is not None: + _fuzzy_identification_stop(t[x < c], t[x >= c]) + # --- Mass points (rdrobust.R:365-380) --- # R's rdrobust() runs this detection ITSELF, before the manual-vs- # data-driven bandwidth branch, so the warning fires on manual-h @@ -620,7 +805,7 @@ def fit( h_l = h_r = float(h_user) b_l = b_r = float(b_resolved) else: - bw = rdbwselect_sharp( + bw = rdbwselect( y, x, c=c, @@ -634,6 +819,8 @@ def fit( bwrestrict=bool(self.bwrestrict), scaleregul=float(self.scaleregul), warn_masspoints=False, # fit() already warned (rdrobust.R:365-380) + fuzzy=t, + sharpbw=bool(self.sharpbw), ) h_l, h_r, b_l, b_r = bw.bws[self.bwselect] n_unique_left = bw.M_l if self.masspoints != "off" else n_unique_left @@ -644,7 +831,7 @@ def fit( b_r = h_r / rho # --- Estimation (port validates h/b finite and positive) --- - fit = rdrobust_fit_sharp( + fit = rdrobust_fit( y, x, c, @@ -657,8 +844,20 @@ def fit( kernel=kernel, vce=self.vcov_type, nnmatch=int(self.nnmatch), + t=t, ) + # Estimand label: the complier-LATE reading requires BINARY + # take-up (plus monotonicity); non-binary (dose) take-up - accepted, + # matching R's fuzzy= - is the ratio-of-jumps estimand and must not + # be labeled a LATE (the label is what `att` claims to measure). + if not fuzzy_fit: + estimand = "sharp (ATE at the cutoff)" + elif t is not None and bool(np.all(np.isin(t, (0.0, 1.0)))): + estimand = "fuzzy (LATE for compliers at the cutoff)" + else: + estimand = "fuzzy (local Wald ratio at the cutoff; non-binary take-up)" + alpha = float(self.alpha) # Three inference rows (rdrobust.R:854-863), each through the # library's joint-NaN gate: @@ -666,6 +865,53 @@ def fit( t_cl, p_cl, ci_cl = safe_inference(fit.tau_cl, fit.se_cl, alpha=alpha) t_bcm, p_bcm, ci_bcm = safe_inference(fit.tau_bc, fit.se_cl, alpha=alpha) + # --- First-stage rows + weak-identification warning (fuzzy) --- + fs: Dict[str, Any] = {} + if fuzzy_fit: + assert fit.tau_T_bc is not None and fit.tau_T_cl is not None + assert fit.se_T_rb is not None and fit.se_T_cl is not None + fs_t_rb, fs_p_rb, fs_ci_rb = safe_inference(fit.tau_T_bc, fit.se_T_rb, alpha=alpha) + fs_t_cl, fs_p_cl, fs_ci_cl = safe_inference(fit.tau_T_cl, fit.se_T_cl, alpha=alpha) + fs_t_bcm, fs_p_bcm, fs_ci_bcm = safe_inference(fit.tau_T_bc, fit.se_T_cl, alpha=alpha) + fs = dict( + first_stage=fit.tau_T_bc, + first_stage_se=fit.se_T_rb, + first_stage_t_stat=fs_t_rb, + first_stage_p_value=fs_p_rb, + first_stage_conf_int=fs_ci_rb, + first_stage_conventional=fit.tau_T_cl, + first_stage_se_conventional=fit.se_T_cl, + first_stage_t_stat_conventional=fs_t_cl, + first_stage_p_value_conventional=fs_p_cl, + first_stage_conf_int_conventional=fs_ci_cl, + first_stage_t_stat_bias_corrected=fs_t_bcm, + first_stage_p_value_bias_corrected=fs_p_bcm, + first_stage_conf_int_bias_corrected=fs_ci_bcm, + beta_t_p_left=fit.beta_t_p_l, + beta_t_p_right=fit.beta_t_p_r, + ) + # Deviation from R (verified silent): warn when the take-up + # jump is not distinguishable from zero at the fit's own alpha + # - the ratio is then unreliable (CCT 2014 Theorem 3 requires + # tau_T != 0; weak-IV-robust inference per Feir-Lemieux-Marmer + # is a documented seam). Gate: FINITE robust CI containing 0, + # so NaN-gated first stages (e.g. perfect compliance's se=0) + # correctly do not fire. + if ( + np.isfinite(fs_ci_rb[0]) + and np.isfinite(fs_ci_rb[1]) + and fs_ci_rb[0] <= 0.0 <= fs_ci_rb[1] + ): + warnings.warn( + "Weak first stage: the take-up discontinuity " + f"({fit.tau_T_bc:.4g}, robust CI [{fs_ci_rb[0]:.4g}, " + f"{fs_ci_rb[1]:.4g}]) is not distinguishable from zero " + f"at alpha={alpha:g}; the fuzzy (ratio) estimates are " + "unreliable under weak identification.", + UserWarning, + stacklevel=2, + ) + return RegressionDiscontinuityResults( att=fit.tau_bc, se=fit.se_rb, @@ -710,6 +956,10 @@ def fit( h_input=None if self.h is None else float(self.h), b_input=None if self.b is None else float(self.b), rho_input=None if self.rho is None else float(self.rho), + estimand=estimand, + sharpbw=bool(self.sharpbw), + treatment_col=treatment_col, beta_p_left=fit.beta_p_l, beta_p_right=fit.beta_p_r, + **fs, ) diff --git a/docs/api/regression_discontinuity.rst b/docs/api/regression_discontinuity.rst index 91cdf4a3..6a8ef715 100644 --- a/docs/api/regression_discontinuity.rst +++ b/docs/api/regression_discontinuity.rst @@ -1,35 +1,43 @@ -Regression Discontinuity (Sharp) -================================ - -Sharp regression discontinuity estimation with robust bias-corrected -inference, parity-targeting R ``rdrobust`` 4.0.0. - -Treatment is assigned by a known threshold of an observed running variable -(``running >= cutoff``; units exactly at the cutoff are treated, matching -rdrobust). The effect is the jump in the conditional expectation of the -outcome at the cutoff, estimated by kernel-weighted local polynomials on -each side with data-driven MSE/CER-optimal bandwidths (all 10 rdrobust -selectors), and reported with robust bias-corrected inference per Calonico, -Cattaneo & Titiunik (2014). +Regression Discontinuity +======================== + +Regression discontinuity estimation - sharp and fuzzy - with robust +bias-corrected inference, parity-targeting R ``rdrobust`` 4.0.0. + +**Sharp** (default): treatment is assigned by a known threshold of an +observed running variable (``running >= cutoff``; units exactly at the +cutoff are treated, matching rdrobust). The effect is the jump in the +conditional expectation of the outcome at the cutoff. **Fuzzy** (pass the +observed take-up column via ``fit(..., treatment_col=...)``): crossing +the cutoff shifts take-up instead of determining it, and the estimand is +the local Wald ratio - under monotonicity, the LATE for compliers at the +cutoff - with the first stage exposed as a full ``first_stage*`` block. +Both designs use kernel-weighted local polynomials on each side with +data-driven MSE/CER-optimal bandwidths (all 10 rdrobust selectors) and +robust bias-corrected inference per Calonico, Cattaneo & Titiunik (2014). .. note:: **Canonical inference binding.** The result's ``att``, ``se``, ``t_stat``, ``p_value``, and ``conf_int`` are one internally coherent row - the ROBUST bias-corrected row (``att`` is the bias-corrected - estimate; ``conf_int`` is centered on it; ``t_stat == att/se``). R's - rdrobust prints the *conventional* estimate as its headline coefficient - while taking inference from the robust row; that estimate is available - as ``att_conventional`` with its own full inference row, and - ``summary()`` prints the familiar three-row rdrobust table. + estimate - the linearized bias-corrected ratio on fuzzy fits; + ``conf_int`` is centered on it; ``t_stat == att/se``). The + ``estimand`` field names what ``att`` measures for the fit at hand. + R's rdrobust prints the *conventional* estimate as its headline + coefficient while taking inference from the robust row; that estimate + is available as ``att_conventional`` with its own full inference row, + and ``summary()`` prints the familiar three-row rdrobust table (plus + a first-stage block on fuzzy fits, as R does). .. note:: - **Scope of this release.** Sharp designs only, with the nearest-neighbor - variance estimator (rdrobust's default). Fuzzy designs, covariate - adjustment, cluster-robust variance, weights, kink estimands, and the - rdplot/density-test diagnostics are documented follow-ups - see the - methodology registry for the full deviations and seams list. + **Scope of this release.** Sharp and fuzzy designs with the + nearest-neighbor variance estimator (rdrobust's default). Covariate + adjustment, cluster-robust variance, weights, kink estimands, + weak-IV-robust fuzzy inference, and the rdplot/density-test + diagnostics are documented follow-ups - see the methodology registry + for the full deviations and seams list. RegressionDiscontinuity ----------------------- diff --git a/docs/choosing_estimator.rst b/docs/choosing_estimator.rst index 6eaa06c4..8d315965 100644 --- a/docs/choosing_estimator.rst +++ b/docs/choosing_estimator.rst @@ -706,7 +706,7 @@ differences helps interpret results and choose appropriate inference. - Two SE regimes per :doc:`api/had`. **Unweighted**: continuous-dose paths use the CCT-2014 robust SE from the in-house ``lprobust`` port; mass-point uses a 2SLS sandwich. **``survey_design=SurveyDesign(weights="col", ...)``** (the sole weighting entry as of the 3.7.0 ``survey=`` / ``weights=`` removal): both paths compose Binder (1983) Taylor-series linearization (``variance_formula="survey_binder_tsl"`` / ``"survey_binder_tsl_2sls"``); the mass-point survey path rejects ``vcov_type="classical"`` (requires ``hc1`` / ``robust=True``), and ``survey_design=`` + ``cluster=`` is rejected outright (route weighted clustering via ``SurveyDesign(weights=, psu=)``; a bare ``cluster=`` gives unweighted CR1). Per-horizon CIs are pointwise; sup-t bands available on the event-study path via ``cband=True`` whenever ``survey_design=`` or ``cluster=`` is supplied. * - ``RegressionDiscontinuity`` - Robust bias-corrected (CCT 2014, NN variance) - - Sharp RD with rdrobust-4.0.0-parity inference. Canonical ``att``/``se``/``conf_int`` are the ROBUST bias-corrected row (``att`` = bias-corrected estimate, CI centered on it); the conventional estimate rdrobust prints as its headline is ``att_conventional`` with its own inference row. Only ``vcov_type="nn"`` in this release; cluster-robust RD variance is a documented follow-up. + - Sharp and fuzzy RD with rdrobust-4.0.0-parity inference (fuzzy via ``fit(..., treatment_col=...)``: local Wald ratio with a linearized bias correction, first-stage block, and a weak-first-stage warning). Canonical ``att``/``se``/``conf_int`` are the ROBUST bias-corrected row (``att`` = bias-corrected estimate, CI centered on it); the conventional estimate rdrobust prints as its headline is ``att_conventional`` with its own inference row. Only ``vcov_type="nn"`` in this release; cluster-robust RD variance is a documented follow-up. * - ``SunAbraham`` - Cluster-robust (unit level) - Clusters at unit level by default. Specify ``cluster`` to override. Use ``n_bootstrap`` for pairs bootstrap inference. diff --git a/docs/doc-deps.yaml b/docs/doc-deps.yaml index 10d2b5c7..0a88f52c 100644 --- a/docs/doc-deps.yaml +++ b/docs/doc-deps.yaml @@ -419,7 +419,7 @@ sources: - path: diff_diff/guides/llms-autonomous.txt section: "Estimator-support matrix + out-of-scope list" type: user_guide - note: "Sharp RD is IN scope as of the RegressionDiscontinuity release; the out-of-scope bullet routes only fuzzy RD elsewhere. Keep both in sync with the estimator's v1 seams." + note: "Sharp AND fuzzy RD are IN scope; the out-of-scope bullet routes only kink designs / covariate-adjusted / cluster-robust RD elsewhere. Keep both in sync with the estimator's v1 seams." - path: docs/choosing_estimator.rst section: "SE methodology + Survey Design Support tables" type: user_guide diff --git a/docs/methodology/REGISTRY.md b/docs/methodology/REGISTRY.md index a56da0a4..495203a2 100644 --- a/docs/methodology/REGISTRY.md +++ b/docs/methodology/REGISTRY.md @@ -3527,6 +3527,35 @@ Defaults reproduce `rdrobust(y, x)`: `p=1`, `q=2`, triangular kernel, `bwselect="mserd"`, `vcov_type="nn"` (J=3), `masspoints="adjust"`, normal-quantile CIs. +**Estimand and estimator (fuzzy RD; `fit(..., treatment_col=...)`):** +crossing the cutoff shifts OBSERVED take-up `T` instead of determining +it; the estimand is the local Wald ratio +`tau_FRD = tau_Y,SRD / tau_T,SRD` (CCT 2014 Section 3.2; identification +requires `tau_T,SRD != 0`, Theorem 3). For binary take-up under +monotonicity this is the LATE for compliers at the cutoff; for +non-binary (dose) take-up - accepted, matching R's `fuzzy=` - it is the +ratio-of-jumps estimand without the complier-LATE reading. The +`estimand` results field is DATA-DEPENDENT accordingly: binary take-up +(values in {0, 1}) reports "fuzzy (LATE for compliers at the cutoff)"; +anything else reports "fuzzy (local Wald ratio at the cutoff; +non-binary take-up)" so the label never overclaims. Implementation stacks `T` as a second response column +through the SAME local-polynomial fits (rdrobust.R:581-591); the bias +correction is the LINEARIZATION of the ratio via the delta vector +`s_Y = [1/tau_T_cl, -tau_Y_cl/tau_T_cl^2]`: +`tau_bc = tau_cl - s_Y . B_F` with +`B_F = [tau_Y_cl - tau_Y_bc, tau_T_cl - tau_T_bc]` (rdrobust.R:636-657) +- NOT a per-component bias-corrected ratio, per CCT 2014's "bias-correct +the first-order linear approximation of the ratio". Variances reuse the +same sandwiches with the (n, 2) residual matrix collapsed by `s_Y` +(ratio) or `sV_T = [0, 1]` (first stage); the Y-T covariance is the +cross term of `res @ s` (functions.R:379-385). The per-side biases are +the linearized `s_Y . B_F_side` (rdrobust.R:649-652). The first stage is +exposed as a full three-row mirror (`first_stage*` fields, robust-row +canonical binding like `att`) and `summary()` prints a first-stage block +above the treatment effects, as R does. A take-up column deterministic +in `X` reproduces the sharp fit (first stage == 1, ULP-level agreement - +the ratio divides by a float-solved 1). + **Bandwidth selection:** all 10 rdrobust data-driven selectors (`mserd` default; `msetwo`/`msesum`/`msecomb1`/`msecomb2`; CER-optimal `cer*` variants = the matching MSE selector's `h` shrunk by @@ -3536,7 +3565,18 @@ chain, per-kernel pilot constants, IK-style regularization, bwrestrict/bwcheck clamps, masspoints unique-count adjustment). Manual `h`/`b`/`rho` semantics mirror rdrobust exactly: `h` alone -> `b = h`; `h`+`rho` -> `b = h/rho` (overriding a supplied `b`, with a warning); -`rho` without `h` applies to the SELECTED bandwidths. +`rho` without `h` applies to the SELECTED bandwidths. Fuzzy bandwidth +logic (CCFT 2017 Section 6, R-exact): by default bandwidths are selected +for the FUZZY RATIO objective - `T` is stacked into every pilot fit and +the pilot ratio + delta vector feed the V/B constants (approach 2); +`sharpbw=True` forces the sharp reduced-form-on-`Y` objective +(approach 1); and ONE-SIDED PERFECT COMPLIANCE (zero take-up variance on +either side) auto-switches to approach 1 regardless of the flag +(`perf_comp`, rdrobust.R:164-185 / rdbwselect.R:334-346) - selection +only; estimation always remains fuzzy. In the port, `T` threads through +the shared `_bw` closure so all three selector chains (mserd, msetwo, +msesum - 14 pilot call sites) receive it; the `msetwo` fuzzy golden +config pins the per-side chains. - **Note (canonical inference binding; deviation from R's printed output):** the result's canonical `att`/`se`/`t_stat`/`p_value`/ @@ -3598,14 +3638,53 @@ bwrestrict/bwcheck clamps, masspoints unique-count adjustment). Manual per-window distinct-support guards above (in the estimation port, ahead of any Gram inversion), not by changing `qrXXinv`'s parity behavior. +- **Deviation from R (weak first stage):** rdrobust is SILENT when the + take-up jump is statistically indistinguishable from zero (verified + live on installed 4.0.0: a near-zero first stage returns a wildly + inflated ratio with no message). The estimator emits a `UserWarning` + when the first-stage ROBUST confidence interval at the fit's own + `alpha` is FINITE and contains 0 - a parameter-free gate (no imported + F-statistic threshold) backed by CCT 2014's Theorem 3 requirement + `tau_T,SRD != 0` and its weak-identification "guard and warn" + discussion citing Feir-Lemieux-Marmer. The finite-CI gate means + NaN-gated first stages (see next Note) never fire the warning. + Weak-IV-ROBUST fuzzy inference (Feir-Lemieux-Marmer 2016) is a + documented seam. +- **Note (first-stage `se = 0`, deviation from R's `Inf`):** perfect + compliance (take-up deterministic in the running variable) gives + exactly-zero first-stage NN residuals, so `se_T = 0` and the + first-stage inference triples NaN-gate via `safe_inference()` where R + prints `z_T = Inf, pv_T = 0`. The first-stage point estimate itself + (== 1) is reported. A DEGENERATE-ZERO first stage under manual + bandwidths (no take-up variation inside the window despite variation + in the sample) follows R's Inf/NaN arithmetic flow-on: the ratio rows + come out non-finite and joint-NaN-gate - loud, not silent. Under + DATA-DRIVEN bandwidths the same degeneracy hits the pilot ratio + (`tau_T == 0` in a pilot window) and fails closed with the targeted + "non-finite pilot bandwidth" error (documented deviation; R flows + Inf/NaN through selection into opaque downstream errors). +- **Deviation from R:** `sharpbw=True` on a SHARP fit warns and is + ignored (R ignores it silently); on manual-bandwidth fuzzy fits it is + a silent no-op in both implementations (selection is skipped). +- **Note (fuzzy identification guard, R-exact):** a take-up variable + with zero variance on BOTH sides and no mean jump at the cutoff raises + R's exact error ("Fuzzy RD: first-stage variable has no variation and + no jump at the cutoff...") in both entry points, and the check runs + BEFORE mass-point detection, matching R's rdrobust ordering + (rdrobust.R:175 precedes :365-380; live-verified that R raises with no + mass-point warning on degenerate fuzzy + tied data). One-sided zero + variance is a legitimate design and routes to the `perf_comp` + bandwidth switch instead. The R `var(T_side) == 0` test is implemented + as exact constancy (R's two-pass `mean()` makes its variance of a + constant vector exactly zero; numpy's single-pass mean does not). - **Note (v1 scope seams):** only `vcov_type="nn"` (rdrobust's default) ships; `hc0`-`hc3` and cluster modes raise `NotImplementedError`. - Fuzzy designs, covariate adjustment (CCFT 2019 - review on file), - weights, kink estimands (`deriv`), `scalepar`, `stdvars`, per-side - manual bandwidths, and the rdplot/density diagnostics are documented - follow-ups; fuzzy/covariates/cluster/weights are not constructor - parameters at all. The port's `deriv` machinery is golden-covered for - `deriv in {0, 1}` only. + Covariate adjustment (CCFT 2019 - review on file), weights, kink + estimands (`deriv`), `scalepar`, `stdvars`, per-side manual + bandwidths, weak-IV-robust fuzzy inference (Feir-Lemieux-Marmer), and + the rdplot/density diagnostics are documented follow-ups; + covariates/cluster/weights are not constructor parameters at all. The + port's `deriv` machinery is golden-covered for `deriv in {0, 1}` only. - **Note (p/q surface, R-exact):** public `p`/`q` validation mirrors rdrobust.R:47-57 exactly - integers in 0:20 with `q > p`; `p=0` is R's local-constant fit and is accepted. R resolves a NULL `q` to `p + 1` @@ -3653,14 +3732,18 @@ bwrestrict/bwcheck clamps, masspoints unique-count adjustment). Manual **Validation:** bandwidth goldens `benchmarks/data/rdrobust_golden.json` (17 configs x 10 selectors, `tests/test_rdrobust_port.py`); estimation -goldens `benchmarks/data/rdrobust_estimates_golden.json` (16 configs, -full three-row blocks + counts + per-side coefficients, -`tests/test_rdd_parity.py`); vendored Senate data -(`benchmarks/data/rdrobust_senate.csv`, Cattaneo-Frandsen-Titiunik 2015) -anchoring the published 2017 Stata Journal numbers under -`masspoints="off"`; R-free methodology anchors in +goldens `benchmarks/data/rdrobust_estimates_golden.json` (23 configs +incl. 7 fuzzy - default/sharpbw/manual-h/epa/msetwo/one-sided-perf_comp/ +ties - with full first-stage three-row blocks; the per-side LINEARIZED +fuzzy biases are pinned at port level in +`tests/test_rdrobust_port.py::TestFuzzyPortGoldenParity`; +`tests/test_rdd_parity.py` pins everything the public results expose); +vendored Senate data (`benchmarks/data/rdrobust_senate.csv`, +Cattaneo-Frandsen-Titiunik 2015) anchoring the published 2017 Stata +Journal numbers under `masspoints="off"`; R-free methodology anchors in `tests/test_rdd_methodology.py` (Remark 7 equivalence, invariances, -NaN/degenerate contracts). +perfect-compliance == sharp, perf_comp/sharpbw bandwidth switches, +weak-first-stage warning gate, NaN/degenerate contracts). **Paper reviews on file:** `docs/methodology/papers/calonico-cattaneo-titiunik-2014-review.md` (CCT 2014, @@ -4264,7 +4347,7 @@ should be a deliberate user choice. | QDiD | qte | `QDiD()` | | BaconDecomposition | bacondecomp | `bacon()` | | HonestDiD | HonestDiD | `createSensitivityResults()` | -| RegressionDiscontinuity | rdrobust | `rdrobust()` + `rdbwselect()` (4.0.0; sharp/nn path) | +| RegressionDiscontinuity | rdrobust | `rdrobust()` + `rdbwselect()` (4.0.0; sharp + fuzzy, nn path; `treatment_col` = R's `fuzzy=`) | | PreTrendsPower | pretrends | `pretrends()` | | PowerAnalysis | pwr / DeclareDesign / pcpanel | `pwr::pwr.norm.test` (analytical, normal-based — D1) + `pcpanel` (Burlig 2020 panel, equicorrelated case) + simulation. The analytical multiplier is normal (z), so `pwr.t.test` is **not** the faithful parity target. | diff --git a/docs/methodology/papers/calonico-cattaneo-farrell-titiunik-2017-review.md b/docs/methodology/papers/calonico-cattaneo-farrell-titiunik-2017-review.md index b209914f..03c8b898 100644 --- a/docs/methodology/papers/calonico-cattaneo-farrell-titiunik-2017-review.md +++ b/docs/methodology/papers/calonico-cattaneo-farrell-titiunik-2017-review.md @@ -188,7 +188,7 @@ Output structure (pp. 392-399): two inference rows — `Conventional` (point est - [ ] `rho()` computes `h` only and sets `b = h/rho` - [ ] IK-style regularization on by default; `scaleregul(0)` removes it - [ ] NN variance uses ALL equidistant neighbors under ties (minimum-J semantics) -- [ ] Fuzzy bandwidth approach auto-switches to sharp under one-sided perfect compliance +- [x] Fuzzy bandwidth approach auto-switches to sharp under one-sided perfect compliance (shipped: `perf_comp` in the port, golden-locked by the one-sided config) - [ ] Senate worked-example smoke-test targets reproduced (see below) --- diff --git a/docs/methodology/papers/calonico-cattaneo-titiunik-2014-review.md b/docs/methodology/papers/calonico-cattaneo-titiunik-2014-review.md index 078d4d48..113e0344 100644 --- a/docs/methodology/papers/calonico-cattaneo-titiunik-2014-review.md +++ b/docs/methodology/papers/calonico-cattaneo-titiunik-2014-review.md @@ -137,7 +137,7 @@ with `V_{nu,p} = (sigma_-^2 + sigma_+^2) nu!^2 e_nu' Gamma_p^{-1} Psi_p Gamma_p^ - [ ] `rho = h/b` reporting; support user-specified `h`, `b`, and `b = h` - [ ] Mass-point detection warning for discrete running variables (Remark 1) - [ ] Optional different bandwidths per side (Remark 9) -- [ ] (Deferred, fuzzy) linearization-based bias correction, denominator guard (Theorems 3-4) +- [x] (Fuzzy) linearization-based bias correction (shipped: `tau_bc = tau_cl - s_Y·B_F`), denominator guard (Theorems 3-4; identification error + weak-first-stage warning shipped; FLM weak-IV-robust inference remains a documented seam) - [ ] (Deferred, kink) `nu = 1` estimands via the general `(nu, p, q)` machinery (Theorem 2) --- diff --git a/docs/references.rst b/docs/references.rst index 5419b701..7730bbfc 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -91,6 +91,10 @@ Nonparametric Bias-Corrected Inference Covariate-adjusted RD (additive common-coefficient specification and its consistency conditions) - the documented fast-follow for :class:`diff_diff.RegressionDiscontinuity`; review on file. +- **Feir, D., Lemieux, T., & Marmer, V. (2016).** "Weak Identification in Fuzzy Regression Discontinuity Designs." *Journal of Business & Economic Statistics*, 34(2), 185-196. https://doi.org/10.1080/07350015.2015.1024836 + + The weak-identification analysis behind :class:`diff_diff.RegressionDiscontinuity`'s fuzzy weak-first-stage warning (CCT 2014 cites the working-paper version); FLM's weak-IV-robust confidence sets are a documented follow-up seam. + Survey-Design Inference (Taylor-Series Linearization) ----------------------------------------------------- diff --git a/tests/test_rdd.py b/tests/test_rdd.py index b68404a2..facd8c5b 100644 --- a/tests/test_rdd.py +++ b/tests/test_rdd.py @@ -36,6 +36,7 @@ def test_constructor_defaults_match_rdrobust(self): "bwcheck": None, "bwrestrict": True, "scaleregul": 1.0, + "sharpbw": False, "alpha": 0.05, } @@ -114,6 +115,12 @@ def test_non_bool_bwrestrict_raises(self): with pytest.raises(ValueError, match="bwrestrict must be a bool"): RegressionDiscontinuity(bwrestrict=1) + def test_non_bool_sharpbw_raises(self): + with pytest.raises(ValueError, match="sharpbw must be a bool"): + RegressionDiscontinuity(sharpbw="True") + with pytest.raises(ValueError, match="sharpbw must be a bool"): + RegressionDiscontinuity(sharpbw=1) + def test_nonpositive_h_raises(self): with pytest.raises(ValueError, match="h must be None or finite"): RegressionDiscontinuity(h=0.0) @@ -357,3 +364,116 @@ def test_all_exports(self): for name in ("RegressionDiscontinuity", "RegressionDiscontinuityResults", "RDD"): assert name in diff_diff.__all__ assert hasattr(diff_diff, name) + + +def _fuzzy_df(n=1200, seed=5): + # Strong first stage (0.15 -> 0.85 take-up at n=1200): the weak-ID + # warning must not fire on this fixture, keeping API tests + # warning-clean. + rng = np.random.default_rng(seed) + x = rng.uniform(-1, 1, n) + t = (rng.uniform(size=n) < np.where(x >= 0, 0.85, 0.15)).astype(float) + y = 0.5 * x + 1.0 * t + rng.standard_normal(n) * 0.2 + return pd.DataFrame({"x": x, "y": y, "t": t}) + + +class TestFuzzyAPI: + def test_missing_treatment_col_raises(self): + with pytest.raises(ValueError, match="not found"): + RegressionDiscontinuity().fit(_fuzzy_df(), "y", "x", treatment_col="takeup") + + def test_nan_in_treatment_counted_in_drop(self): + df = _fuzzy_df(200) + df.loc[3, "t"] = np.nan + df.loc[7, "y"] = np.nan + with pytest.warns(UserWarning, match="Dropping 2 row"): + r = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + assert r.n_obs == 198 + assert r.n_dropped == 2 + + def test_estimand_echo(self): + df = _fuzzy_df() + sharp = RegressionDiscontinuity().fit(df, "y", "x") + fz = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + assert sharp.estimand == "sharp (ATE at the cutoff)" + assert fz.estimand == "fuzzy (LATE for compliers at the cutoff)" + assert sharp.treatment_col is None + assert fz.treatment_col == "t" + + def test_estimand_label_non_binary_takeup(self): + # Dose take-up is accepted (R's fuzzy= semantics) but must NOT be + # labeled a complier LATE - the label is data-dependent so it + # never overclaims what att measures. + df = _fuzzy_df(400) + rng = np.random.default_rng(6) + df["dose"] = df["t"] * rng.uniform(0.5, 2.0, size=len(df)) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + r = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="dose") + assert r.estimand == "fuzzy (local Wald ratio at the cutoff; non-binary take-up)" + assert r.first_stage is not None + + def test_first_stage_fields_none_on_sharp(self): + r = RegressionDiscontinuity().fit(_fuzzy_df(), "y", "x") + for name in ( + "first_stage", + "first_stage_se", + "first_stage_t_stat", + "first_stage_p_value", + "first_stage_conf_int", + "first_stage_conventional", + "first_stage_se_conventional", + "first_stage_t_stat_conventional", + "first_stage_p_value_conventional", + "first_stage_conf_int_conventional", + "first_stage_t_stat_bias_corrected", + "first_stage_p_value_bias_corrected", + "first_stage_conf_int_bias_corrected", + "beta_t_p_left", + "beta_t_p_right", + ): + assert getattr(r, name) is None, name + d = r.to_dict() + assert d["first_stage"] is None + assert d["first_stage_conf_int_lower"] is None # None-safe CI split + + def test_first_stage_populated_and_coherent_on_fuzzy(self): + r = RegressionDiscontinuity().fit(_fuzzy_df(), "y", "x", treatment_col="t") + assert r.first_stage is not None and r.first_stage_se is not None + assert r.first_stage_t_stat == pytest.approx(r.first_stage / r.first_stage_se, rel=1e-14) + lo, hi = r.first_stage_conf_int + assert (lo + hi) / 2 == pytest.approx(r.first_stage, rel=1e-12) + d = r.to_dict() + assert d["first_stage_conf_int_lower"] == lo + + def test_sharpbw_on_sharp_fit_warns_and_ignored(self): + df = _fuzzy_df() + with pytest.warns(UserWarning, match="sharpbw has no effect"): + r = RegressionDiscontinuity(sharpbw=True).fit(df, "y", "x") + ref = RegressionDiscontinuity().fit(df, "y", "x") + assert r.att == ref.att and r.h_left == ref.h_left + + def test_summary_first_stage_block_fuzzy_only(self): + df = _fuzzy_df() + fz = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + sharp = RegressionDiscontinuity().fit(df, "y", "x") + assert "First-stage estimates" in fz.summary() + assert "Fuzzy Regression Discontinuity" in fz.summary() + assert "First-stage estimates" not in sharp.summary() + assert "Sharp Regression Discontinuity" in sharp.summary() + assert "Estimand:" in sharp.summary() + + def test_canonical_identities_hold_on_fuzzy(self): + r = RegressionDiscontinuity().fit(_fuzzy_df(), "y", "x", treatment_col="t") + assert r.t_stat == pytest.approx(r.att / r.se, rel=1e-14) + lo, hi = r.conf_int + assert (lo + hi) / 2 == pytest.approx(r.att, rel=1e-12) + assert r.se == r.se_robust + + def test_sharpbw_set_params_roundtrip(self): + rd = RegressionDiscontinuity() + rd.set_params(sharpbw=True) + assert rd.sharpbw is True + with pytest.raises(ValueError): + rd.set_params(sharpbw="yes") + assert rd.sharpbw is True # transactional: unchanged after failure diff --git a/tests/test_rdd_methodology.py b/tests/test_rdd_methodology.py index 87b7544e..d81726d5 100644 --- a/tests/test_rdd_methodology.py +++ b/tests/test_rdd_methodology.py @@ -211,3 +211,195 @@ def test_huge_rho_empty_selected_b_fails_closed(self): df = _df(500, seed=9) with pytest.raises(ValueError, match="bias bandwidth window"): RegressionDiscontinuity(rho=1e12).fit(df, "y", "x") + + +def _fuzzy_df(n=1500, seed=9, lo=0.15, hi=0.75, effect=1.2): + rng = np.random.default_rng(seed) + x = 2 * rng.beta(2, 4, n) - 1 + t = (rng.uniform(size=n) < np.where(x >= 0, hi, lo)).astype(float) + y = 0.5 * x + effect * t + rng.standard_normal(n) * 0.3 + return pd.DataFrame({"x": x, "y": y, "t": t}) + + +class TestFuzzy: + def test_perfect_compliance_reproduces_sharp_exactly(self): + # T deterministic in the running variable: the first stage is + # EXACTLY 1 (constant-0 left fit, constant-1 right fit), the ratio + # collapses to the sharp estimate, and R selects the sharp + # bandwidths via perf_comp - verified equal on installed 4.0.0. + df = _fuzzy_df(800, seed=11) + df["t"] = (df["x"] >= 0).astype(float) + with warnings.catch_warnings(record=True) as rec: + warnings.simplefilter("always") + fz = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + sharp = RegressionDiscontinuity().fit(df, "y", "x") + # Bandwidths are BIT-identical (perf_comp nulls T, so selection is + # the same sharp arithmetic); the estimates agree to the ULP - the + # ratio divides by a first stage of 1 - O(eps) from the float + # solve, in R exactly as here. + assert fz.h_left == sharp.h_left and fz.b_left == sharp.b_left + assert fz.att == pytest.approx(sharp.att, rel=1e-12) + assert fz.se == pytest.approx(sharp.se, rel=1e-12) + assert fz.first_stage == pytest.approx(1.0, rel=1e-12) + assert fz.first_stage_conventional == pytest.approx(1.0, rel=1e-12) + # Zero first-stage NN residuals -> se_T = 0 -> the first-stage + # inference triple NaN-gates (documented deviation from R's + # z_T = Inf, pv_T = 0)... + assert fz.first_stage_se == 0.0 + assert_nan_inference( + { + "se": np.nan, # gate is on the triple below, se itself is 0 + "t_stat": fz.first_stage_t_stat, + "p_value": fz.first_stage_p_value, + "conf_int": fz.first_stage_conf_int, + } + ) + # ...and the weak-first-stage warning must NOT fire (a perfect + # first stage is the opposite of weak; the finite-CI gate holds). + assert not any("Weak first stage" in str(w.message) for w in rec) + + def test_one_sided_compliance_selects_sharp_bandwidths(self): + # var(T) == 0 on one side -> perf_comp -> bandwidth selection runs + # on the sharp reduced-form objective (rdbwselect.R:334-346); + # estimation stays fuzzy. + df = _fuzzy_df(1200, seed=12) + df.loc[df["x"] < 0, "t"] = 0.0 + fz = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + sharp = RegressionDiscontinuity().fit(df, "y", "x") + assert fz.h_left == sharp.h_left and fz.b_right == sharp.b_right + assert fz.first_stage is not None and fz.first_stage > 0 + assert fz.att != sharp.att # estimation is still the fuzzy ratio + + def test_sharpbw_true_selects_sharp_bandwidths(self): + df = _fuzzy_df(1200, seed=14) + fz_sbw = RegressionDiscontinuity(sharpbw=True).fit(df, "y", "x", treatment_col="t") + fz_def = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + sharp = RegressionDiscontinuity().fit(df, "y", "x") + assert fz_sbw.h_left == sharp.h_left + assert fz_sbw.h_left != fz_def.h_left # fuzzy objective differs + + def test_outcome_scaling_scales_ratio_not_first_stage(self): + df = _fuzzy_df(900, seed=15) + scaled = df.assign(y=df.y * 7.0) + a = RegressionDiscontinuity(h=0.3).fit(df, "y", "x", treatment_col="t") + b = RegressionDiscontinuity(h=0.3).fit(scaled, "y", "x", treatment_col="t") + assert b.att == pytest.approx(7.0 * a.att, rel=1e-12) + assert b.se == pytest.approx(7.0 * a.se, rel=1e-12) + # The take-up fits never see y: bit-identical first stage. + assert b.first_stage == a.first_stage + assert b.first_stage_se == a.first_stage_se + + def test_no_variation_no_jump_raises(self): + # R-exact message; and the identification stop is hoisted BEFORE + # mass-point detection (rdrobust.R:175 precedes :365-380), so no + # mass-point warning precedes the raise even on tied data. + df = _fuzzy_df(600, seed=16) + df["x"] = df["x"].round(2) # heavy ties + df["t"] = 0.7 + with warnings.catch_warnings(record=True) as rec: + warnings.simplefilter("always") + with pytest.raises(ValueError, match="no variation and no jump"): + RegressionDiscontinuity(masspoints="check").fit(df, "y", "x", treatment_col="t") + assert not any("Mass points detected" in str(w.message) for w in rec) + + def test_weak_first_stage_warns(self): + # Take-up independent of the running variable: the jump is not + # distinguishable from zero and R is verified SILENT - we warn + # (documented deviation; CCT 2014 Theorem 3 / FLM weak-ID). + rng = np.random.default_rng(17) + df = _fuzzy_df(800, seed=17) + df["t"] = (rng.uniform(size=800) < 0.4).astype(float) + with pytest.warns(UserWarning, match="Weak first stage"): + RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + + def test_strong_first_stage_no_warning(self): + df = _fuzzy_df(1500, seed=18) + with warnings.catch_warnings(record=True) as rec: + warnings.simplefilter("always") + RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + assert not any("Weak first stage" in str(w.message) for w in rec) + + def test_degenerate_pilot_first_stage_fails_closed(self): + # Take-up varies only far from the cutoff (identification passes, + # no perf_comp) but pilot windows hold zero take-up variation -> + # pilot tau_T == 0 -> R flows Inf/NaN; the port fails closed on + # the non-finite pilot bandwidth (documented deviation). + rng = np.random.default_rng(21) + n = 1000 + x = rng.uniform(-1, 1, n) + t = np.where(np.abs(x) > 0.8, (rng.uniform(size=n) < 0.5).astype(float), 0.0) + y = 0.4 * x + 0.9 * t + rng.standard_normal(n) * 0.2 + df = pd.DataFrame({"x": x, "y": y, "t": t}) + with pytest.raises(ValueError, match="non-finite pilot bandwidth"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + + def test_manual_h_zero_first_stage_nan_gates(self): + # Same construction with a manual h: estimation-time tau_T == 0 + # follows R's Inf/NaN flow-on and the main rows joint-NaN gate + # (loud, not silent); the first stage itself is exactly 0 with a + # zero SE (NaN-gated triple), so the weak-ID warning cannot fire. + rng = np.random.default_rng(21) + n = 1000 + x = rng.uniform(-1, 1, n) + t = np.where(np.abs(x) > 0.8, (rng.uniform(size=n) < 0.5).astype(float), 0.0) + y = 0.4 * x + 0.9 * t + rng.standard_normal(n) * 0.2 + df = pd.DataFrame({"x": x, "y": y, "t": t}) + with warnings.catch_warnings(record=True) as rec: + warnings.simplefilter("always") + r = RegressionDiscontinuity(h=0.3).fit(df, "y", "x", treatment_col="t") + assert np.isnan(r.att) + assert_nan_inference( + {"se": r.se, "t_stat": r.t_stat, "p_value": r.p_value, "conf_int": r.conf_int} + ) + assert r.first_stage == 0.0 + assert not any("Weak first stage" in str(w.message) for w in rec) + + def test_n_below_20_fuzzy_estimates_fuzzy(self): + # The full-range fallback still produces a FUZZY fit (first-stage + # fields populated, resolved label "Manual"). + rng = np.random.default_rng(19) + x = np.linspace(-1, 1, 15) + t = np.array([0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1], dtype=float) + y = 0.5 * x + 1.0 * t + rng.standard_normal(15) * 0.1 + df = pd.DataFrame({"x": x, "y": y, "t": t}) + with pytest.warns(UserWarning, match="entire sample"): + r = RegressionDiscontinuity().fit(df, "y", "x", treatment_col="t") + assert r.bwselect == "Manual" + assert r.first_stage is not None + assert r.estimand.startswith("fuzzy") + + def test_manual_h_sharpbw_is_silent_noop(self): + # Manual bandwidths skip selection entirely, so sharpbw is inert + # on fuzzy fits - and silently so, matching R (the sharp-fit + # warning is only for treatment_col=None). + df = _fuzzy_df(900, seed=20) + with warnings.catch_warnings(record=True) as rec: + warnings.simplefilter("always") + a = RegressionDiscontinuity(h=0.3, sharpbw=True).fit(df, "y", "x", treatment_col="t") + b = RegressionDiscontinuity(h=0.3).fit(df, "y", "x", treatment_col="t") + assert a.att == b.att and a.se == b.se + assert not any("sharpbw" in str(w.message) for w in rec) + + def test_constant_outcome_fuzzy_manual_h(self): + # Constant y: unlike the sharp case (exact zero residuals -> se = 0 + # -> NaN gate), the fuzzy ratio's delta vector carries a + # tiny-but-nonzero -tau_Y/tau_T^2 component from float solve + # roundoff, so the main rows report an O(eps) estimate with an + # O(eps) SE - the semantically correct "no effect" conclusion, not + # a gate (R's arithmetic behaves identically). The FIRST-STAGE + # rows are unaffected by y and stay properly finite. + rng = np.random.default_rng(22) + n = 400 + x = rng.uniform(-1, 1, n) + t = (rng.uniform(size=n) < np.where(x >= 0, 0.8, 0.2)).astype(float) + df = pd.DataFrame({"x": x, "y": np.ones(n), "t": t}) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + r = RegressionDiscontinuity(h=0.5).fit(df, "y", "x", treatment_col="t") + assert r.att == pytest.approx(0.0, abs=1e-12) + assert abs(r.se) < 1e-12 # O(eps) scale, may not be exactly zero + assert np.isfinite(r.first_stage) and np.isfinite(r.first_stage_se) + assert r.first_stage_se > 0 + assert np.isfinite(r.first_stage_t_stat) diff --git a/tests/test_rdd_parity.py b/tests/test_rdd_parity.py index c63fb6ca..34643e10 100644 --- a/tests/test_rdd_parity.py +++ b/tests/test_rdd_parity.py @@ -36,7 +36,7 @@ def golden(): return json.load(f) -def _frame(golden, dgp_name): +def _frame(golden, dgp_name, cfg_name=None): entry = golden[dgp_name] if dgp_name == "senate": csv_path = Path(__file__).resolve().parents[1] / entry["csv"] @@ -44,6 +44,13 @@ def _frame(golden, dgp_name): pytest.skip(f"Vendored Senate CSV not found at {csv_path}") df = pd.read_csv(csv_path)[["vote", "margin"]].dropna() return df.rename(columns={"vote": "y", "margin": "x"}) + if dgp_name == "dgp_fuzzy": + # Config-specific variants share the same seeded base draw: + # ties_adjust rounds x to 2dp; one_sided zeroes take-up left of + # the cutoff (perf_comp path). + x = entry["x_ties"] if cfg_name == "ties_adjust" else entry["x"] + t = entry["t_one"] if cfg_name == "one_sided" else entry["t"] + return pd.DataFrame({"x": x, "y": entry["y"], "t": t}) return pd.DataFrame({"x": entry["x"], "y": entry["y"]}) @@ -55,6 +62,7 @@ def _kwargs_from_config(cfg): kernel=cfg["kernel"], bwselect=cfg["bwselect"], masspoints=cfg["masspoints"], + sharpbw=bool(cfg["sharpbw"]), alpha=1 - cfg["level"] / 100.0, ) if cfg["h_in"] is not None: @@ -66,11 +74,14 @@ def _kwargs_from_config(cfg): return kwargs -def _fit(golden, dgp_name, cfg): - df = _frame(golden, dgp_name) +def _fit(golden, dgp_name, cfg, cfg_name=None): + df = _frame(golden, dgp_name, cfg_name) + treatment_col = "t" if cfg.get("fuzzy_in") else None with warnings.catch_warnings(): warnings.simplefilter("ignore") - return RegressionDiscontinuity(**_kwargs_from_config(cfg)).fit(df, "y", "x") + return RegressionDiscontinuity(**_kwargs_from_config(cfg)).fit( + df, "y", "x", treatment_col=treatment_col + ) class TestEstimateGoldenParity: @@ -81,7 +92,7 @@ def test_all_configs(self, golden): continue for cfg_name, cfg in entry["configs"].items(): label = f"{dgp_name}/{cfg_name}" - r = _fit(golden, dgp_name, cfg) + r = _fit(golden, dgp_name, cfg, cfg_name) pairs = [ ("tau_cl", r.att_conventional, cfg["tau_cl"]), ("tau_bc", r.att, cfg["tau_bc"]), @@ -104,6 +115,43 @@ def test_all_configs(self, golden): ("ci_rb_lo", r.conf_int[0], cfg["ci_lower"][2]), ("ci_rb_hi", r.conf_int[1], cfg["ci_upper"][2]), ] + if cfg.get("fuzzy_in"): + # First-stage three-row block (R's tau_T/se_T/z_T/pv_T/ + # ci_T layout: rows = Conventional/Bias-Corrected/Robust). + pairs += [ + ("fs_cl", r.first_stage_conventional, cfg["tau_T"][0]), + ("fs_bc", r.first_stage, cfg["tau_T"][1]), + ("fs_se_cl", r.first_stage_se_conventional, cfg["se_T"][0]), + ("fs_se_rb", r.first_stage_se, cfg["se_T"][2]), + ("fs_z_cl", r.first_stage_t_stat_conventional, cfg["z_T"][0]), + ("fs_z_bc", r.first_stage_t_stat_bias_corrected, cfg["z_T"][1]), + ("fs_z_rb", r.first_stage_t_stat, cfg["z_T"][2]), + ("fs_pv_cl", r.first_stage_p_value_conventional, cfg["pv_T"][0]), + ("fs_pv_bc", r.first_stage_p_value_bias_corrected, cfg["pv_T"][1]), + ("fs_pv_rb", r.first_stage_p_value, cfg["pv_T"][2]), + ( + "fs_ci_cl_lo", + r.first_stage_conf_int_conventional[0], + cfg["ci_T_lower"][0], + ), + ( + "fs_ci_cl_hi", + r.first_stage_conf_int_conventional[1], + cfg["ci_T_upper"][0], + ), + ( + "fs_ci_bc_lo", + r.first_stage_conf_int_bias_corrected[0], + cfg["ci_T_lower"][1], + ), + ( + "fs_ci_bc_hi", + r.first_stage_conf_int_bias_corrected[1], + cfg["ci_T_upper"][1], + ), + ("fs_ci_rb_lo", r.first_stage_conf_int[0], cfg["ci_T_lower"][2]), + ("fs_ci_rb_hi", r.first_stage_conf_int[1], cfg["ci_T_upper"][2]), + ] for name, got, want in pairs: assert got == pytest.approx( want, rel=RTOL, abs=1e-12 @@ -114,9 +162,16 @@ def test_all_configs(self, golden): np.testing.assert_allclose( r.beta_p_right, cfg["beta_p_r"], rtol=RTOL, err_msg=label ) + if cfg.get("fuzzy_in"): + np.testing.assert_allclose( + r.beta_t_p_left, cfg["beta_t_p_l"], rtol=RTOL, err_msg=label + ) + np.testing.assert_allclose( + r.beta_t_p_right, cfg["beta_t_p_r"], rtol=RTOL, err_msg=label + ) n_checked += 1 - # 16 configurations; fail loudly if the golden shrinks. - assert n_checked == 16 + # 23 configurations; fail loudly if the golden shrinks. + assert n_checked == 23 class TestSenatePublished2017: diff --git a/tests/test_rdrobust_port.py b/tests/test_rdrobust_port.py index 041410a8..e5f948de 100644 --- a/tests/test_rdrobust_port.py +++ b/tests/test_rdrobust_port.py @@ -27,8 +27,8 @@ compute_dups_dupsid, qrXXinv, quantile_type2, - rdbwselect_sharp, - rdrobust_fit_sharp, + rdbwselect, + rdrobust_fit, rdrobust_kweight, rdrobust_res_nn, rdrobust_vander, @@ -272,7 +272,7 @@ def test_all_configs_all_selectors(self, golden): kwargs["bwcheck"] = int(cfg["bwcheck"]) with warnings.catch_warnings(): warnings.simplefilter("ignore") - out = rdbwselect_sharp(y, x, **kwargs) + out = rdbwselect(y, x, **kwargs) assert out.N == cfg["N"], label if cfg["masspoints"] == "off" and cfg["bwcheck"] is None: # Under masspoints='off' without an explicit bwcheck, R never @@ -298,8 +298,8 @@ def test_smooth_dgp_adjust_equals_off(self, golden): entry = golden["dgp_lee_smooth"] x = np.asarray(entry["x"], dtype=np.float64) y = np.asarray(entry["y"], dtype=np.float64) - a = rdbwselect_sharp(y, x, masspoints="adjust") - o = rdbwselect_sharp(y, x, masspoints="off") + a = rdbwselect(y, x, masspoints="adjust") + o = rdbwselect(y, x, masspoints="off") for sel in BWSELECT_OPTIONS: np.testing.assert_array_equal(a.bws[sel], o.bws[sel]) @@ -308,7 +308,7 @@ def test_cer_reuses_mse_pilot_bandwidth(self, golden): entry = golden["dgp_lee_smooth"] x = np.asarray(entry["x"], dtype=np.float64) y = np.asarray(entry["y"], dtype=np.float64) - out = rdbwselect_sharp(y, x) + out = rdbwselect(y, x) for mse, cer in [ ("mserd", "cerrd"), ("msesum", "cersum"), @@ -329,7 +329,7 @@ class TestSenatePublished2017: def test_masspoints_off_matches_stata_journal_2017(self, golden): y, x = _senate_xy(golden) - out = rdbwselect_sharp(y, x, masspoints="off") + out = rdbwselect(y, x, masspoints="off") assert out.bws["mserd"][0] == pytest.approx(17.708, abs=1e-3) assert out.bws["mserd"][2] == pytest.approx(27.984, abs=1e-3) # rdbwselect ..., all output table (Stata Journal 2017, p. 400) @@ -349,94 +349,94 @@ def _xy(self, n=100, seed=0): def test_n_below_20_raises(self): y, x = self._xy(19) with pytest.raises(ValueError, match="Not enough observations"): - rdbwselect_sharp(y, x) + rdbwselect(y, x) def test_one_sided_data_raises(self): y, x = self._xy(50) with pytest.raises(ValueError, match="one side of the cutoff"): - rdbwselect_sharp(y, np.abs(x) + 1.0) + rdbwselect(y, np.abs(x) + 1.0) def test_non_finite_raises(self): y, x = self._xy(50) y[3] = np.nan with pytest.raises(ValueError, match="finite and complete-case"): - rdbwselect_sharp(y, x) + rdbwselect(y, x) def test_length_mismatch_raises(self): y, x = self._xy(50) with pytest.raises(ValueError, match="equal length"): - rdbwselect_sharp(y[:-1], x) + rdbwselect(y[:-1], x) def test_zero_variance_running_var_raises(self): y, _ = self._xy(50) with pytest.raises(ValueError, match="zero variance"): - rdbwselect_sharp(y, np.zeros(50)) + rdbwselect(y, np.zeros(50)) def test_zero_variance_running_var_raises_under_stdvars(self): # Guard must fire BEFORE the stdvars division, not surface as a # divide-by-zero or a misleading one-sided-data error. y, _ = self._xy(50) with pytest.raises(ValueError, match="zero variance"): - rdbwselect_sharp(y, np.zeros(50), stdvars=True) + rdbwselect(y, np.zeros(50), stdvars=True) def test_zero_variance_outcome_raises_under_stdvars(self): _, x = self._xy(50) with pytest.raises(ValueError, match="outcome has zero variance"): - rdbwselect_sharp(np.ones(50), x, stdvars=True) + rdbwselect(np.ones(50), x, stdvars=True) def test_two_dimensional_input_rejected(self): y, x = self._xy(50) with pytest.raises(ValueError, match="1-D vector"): - rdbwselect_sharp(y.reshape(10, 5), x.reshape(10, 5)) + rdbwselect(y.reshape(10, 5), x.reshape(10, 5)) def test_column_vector_input_accepted(self): y, x = self._xy(50) - a = rdbwselect_sharp(y, x) - b = rdbwselect_sharp(y.reshape(-1, 1), x.reshape(-1, 1)) + a = rdbwselect(y, x) + b = rdbwselect(y.reshape(-1, 1), x.reshape(-1, 1)) for sel in BWSELECT_OPTIONS: np.testing.assert_array_equal(a.bws[sel], b.bws[sel]) def test_vce_hc_raises_not_implemented(self): y, x = self._xy(50) with pytest.raises(NotImplementedError, match="vce='nn'"): - rdbwselect_sharp(y, x, vce="hc1") + rdbwselect(y, x, vce="hc1") def test_invalid_masspoints_raises(self): y, x = self._xy(50) with pytest.raises(ValueError, match="masspoints"): - rdbwselect_sharp(y, x, masspoints="on") + rdbwselect(y, x, masspoints="on") def test_invalid_orders_raise(self): y, x = self._xy(50) with pytest.raises(ValueError, match="deriv <= p < q"): - rdbwselect_sharp(y, x, p=2, q=2) + rdbwselect(y, x, p=2, q=2) def test_nnmatch_below_one_raises(self): y, x = self._xy(50) with pytest.raises(ValueError, match="nnmatch"): - rdbwselect_sharp(y, x, nnmatch=0) + rdbwselect(y, x, nnmatch=0) def test_bwcheck_below_one_raises(self): y, x = self._xy(50) with pytest.raises(ValueError, match="bwcheck"): - rdbwselect_sharp(y, x, bwcheck=0) + rdbwselect(y, x, bwcheck=0) def test_negative_scaleregul_raises(self): y, x = self._xy(50) with pytest.raises(ValueError, match="scaleregul"): - rdbwselect_sharp(y, x, scaleregul=-1.0) + rdbwselect(y, x, scaleregul=-1.0) def test_non_integer_orders_raise(self): y, x = self._xy(50) with pytest.raises(ValueError, match="must be an integer"): - rdbwselect_sharp(y, x, p=1.5, q=2.5) # type: ignore[arg-type] + rdbwselect(y, x, p=1.5, q=2.5) # type: ignore[arg-type] def test_mass_warning_fires_on_ties(self, golden): entry = golden["dgp_ties_moderate"] x = np.asarray(entry["x"], dtype=np.float64) y = np.asarray(entry["y"], dtype=np.float64) with pytest.warns(UserWarning, match="Mass points detected"): - rdbwselect_sharp(y, x, masspoints="adjust") + rdbwselect(y, x, masspoints="adjust") def test_check_mode_suggests_adjust(self, golden): entry = golden["dgp_ties_moderate"] @@ -446,7 +446,7 @@ def test_check_mode_suggests_adjust(self, golden): # suggestion); capture the full record so neither leaks into the # pytest warning summary. with pytest.warns(UserWarning) as record: - rdbwselect_sharp(y, x, masspoints="check") + rdbwselect(y, x, masspoints="check") messages = [str(w.message) for w in record] assert any("Mass points detected" in m for m in messages) assert any("masspoints='adjust'" in m for m in messages) @@ -457,8 +457,8 @@ def test_adjust_injects_bwcheck_10(self, golden): y = np.asarray(entry["y"], dtype=np.float64) with warnings.catch_warnings(): warnings.simplefilter("ignore") - out = rdbwselect_sharp(y, x, masspoints="adjust") - out_off = rdbwselect_sharp(y, x, masspoints="off") + out = rdbwselect(y, x, masspoints="adjust") + out_off = rdbwselect(y, x, masspoints="off") assert out.bwcheck_effective == 10 assert out_off.bwcheck_effective is None @@ -470,14 +470,14 @@ def test_unsorted_input_matches_sorted(self, golden): perm = rng.permutation(x.shape[0]) with warnings.catch_warnings(): warnings.simplefilter("ignore") - a = rdbwselect_sharp(y, x) - b = rdbwselect_sharp(y[perm], x[perm]) + a = rdbwselect(y, x) + b = rdbwselect(y[perm], x[perm]) for sel in BWSELECT_OPTIONS: np.testing.assert_allclose(a.bws[sel], b.bws[sel], rtol=1e-12) class TestRdrobustFitSharpValidation: - """rdrobust_fit_sharp shares rdbwselect_sharp's input contract; direct + """rdrobust_fit shares rdbwselect's input contract; direct (non-estimator) callers get targeted errors, not opaque NumPy ones.""" def _yx(self, n=200, seed=11): @@ -489,32 +489,134 @@ def _yx(self, n=200, seed=11): def test_two_dim_input_rejected(self): y, x = self._yx() with pytest.raises(ValueError, match="1-D vector"): - rdrobust_fit_sharp(y.reshape(50, 4), x, 0.0, 0.5, 0.5, 0.5, 0.5) + rdrobust_fit(y.reshape(50, 4), x, 0.0, 0.5, 0.5, 0.5, 0.5) def test_unequal_lengths_rejected(self): y, x = self._yx() with pytest.raises(ValueError, match="equal length"): - rdrobust_fit_sharp(y[:-5], x, 0.0, 0.5, 0.5, 0.5, 0.5) + rdrobust_fit(y[:-5], x, 0.0, 0.5, 0.5, 0.5, 0.5) def test_non_integer_orders_rejected(self): y, x = self._yx() with pytest.raises(ValueError, match="p must be an integer"): - rdrobust_fit_sharp(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, p=1.0) + rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, p=1.0) def test_order_inequality_enforced(self): y, x = self._yx() with pytest.raises(ValueError, match="0 <= deriv <= p < q"): - rdrobust_fit_sharp(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, p=2, q=2) + rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, p=2, q=2) with pytest.raises(ValueError, match="0 <= deriv <= p < q"): - rdrobust_fit_sharp(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, deriv=2, p=1, q=2) + rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, deriv=2, p=1, q=2) def test_nnmatch_validated(self): y, x = self._yx() with pytest.raises(ValueError, match="nnmatch must be an integer >= 1"): - rdrobust_fit_sharp(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, nnmatch=0) + rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, nnmatch=0) def test_column_vector_accepted(self): y, x = self._yx() - a = rdrobust_fit_sharp(y, x, 0.0, 0.5, 0.5, 0.5, 0.5) - b = rdrobust_fit_sharp(y.reshape(-1, 1), x.reshape(-1, 1), 0.0, 0.5, 0.5, 0.5, 0.5) + a = rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5) + b = rdrobust_fit(y.reshape(-1, 1), x.reshape(-1, 1), 0.0, 0.5, 0.5, 0.5, 0.5) assert a.tau_bc == b.tau_bc and a.se_rb == b.se_rb + + +ESTIMATES_GOLDEN_PATH = ( + Path(__file__).resolve().parents[1] / "benchmarks" / "data" / "rdrobust_estimates_golden.json" +) + + +@pytest.fixture(scope="module") +def estimates_golden(): + if not ESTIMATES_GOLDEN_PATH.exists(): + pytest.skip( + "Estimates golden file not found; run: " + "Rscript benchmarks/R/generate_rdrobust_estimates_golden.R" + ) + with open(ESTIMATES_GOLDEN_PATH) as f: + return json.load(f) + + +class TestFuzzyPortGoldenParity: + """Port-level fuzzy parity incl. the per-side LINEARIZED biases + (bias_side = s_Y . B_F_side, rdrobust.R:649-652), which the public + results object does not expose - pinned here so the fuzzy bias formula + cannot silently regress to the sharp per-component difference.""" + + def test_fuzzy_configs_with_bias(self, estimates_golden): + entry = estimates_golden["dgp_fuzzy"] + y = np.array(entry["y"]) + n_checked = 0 + for name, cfg in entry["configs"].items(): + x = np.array(entry["x_ties"] if name == "ties_adjust" else entry["x"]) + t = np.array(entry["t_one"] if name == "one_sided" else entry["t"], dtype=np.float64) + if cfg["h_in"] is not None: + h_l = h_r = b_l = b_r = float(cfg["h_in"]) # h alone -> b = h + else: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + bw = rdbwselect( + y, + x, + kernel=cfg["kernel"], + masspoints=cfg["masspoints"], + fuzzy=t, + sharpbw=bool(cfg["sharpbw"]), + ) + h_l, h_r, b_l, b_r = bw.bws[cfg["bwselect"]] + fit = rdrobust_fit(y, x, 0.0, h_l, h_r, b_l, b_r, kernel=cfg["kernel"], t=t) + for label, got, want in ( + ("h_l", h_l, cfg["h_l"]), + ("b_l", b_l, cfg["b_l"]), + ("tau_cl", fit.tau_cl, cfg["tau_cl"]), + ("tau_bc", fit.tau_bc, cfg["tau_bc"]), + ("se_cl", fit.se_cl, cfg["se_cl"]), + ("se_rb", fit.se_rb, cfg["se_rb"]), + ("tau_T_cl", fit.tau_T_cl, cfg["tau_T"][0]), + ("tau_T_bc", fit.tau_T_bc, cfg["tau_T"][1]), + ("se_T_cl", fit.se_T_cl, cfg["se_T"][0]), + ("se_T_rb", fit.se_T_rb, cfg["se_T"][2]), + ("bias_l", fit.bias_l, cfg["bias"][0]), + ("bias_r", fit.bias_r, cfg["bias"][1]), + ): + assert got == pytest.approx( + want, rel=1e-9, abs=1e-12 + ), f"{name}:{label}: {got} vs {want}" + n_checked += 1 + assert n_checked == 7 + + +class TestFuzzyPortValidation: + def _yxt(self, n=200, seed=13): + rng = np.random.default_rng(seed) + x = rng.uniform(-1, 1, n) + t = (rng.uniform(size=n) < np.where(x >= 0, 0.8, 0.2)).astype(float) + y = 0.3 * x + t + rng.normal(0, 0.1, n) + return y, x, t + + def test_two_dim_t_rejected(self): + y, x, t = self._yxt() + with pytest.raises(ValueError, match="1-D vector"): + rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, t=t.reshape(50, 4)) + with pytest.raises(ValueError, match="1-D vector"): + rdbwselect(y, x, fuzzy=t.reshape(50, 4)) + + def test_t_length_mismatch_rejected(self): + y, x, t = self._yxt() + with pytest.raises(ValueError, match="length equal to x"): + rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, t=t[:-3]) + with pytest.raises(ValueError, match="length equal to x"): + rdbwselect(y, x, fuzzy=t[:-3]) + + def test_identification_stop_reachable_directly(self): + y, x, _ = self._yxt() + const = np.full_like(x, 0.7) + with pytest.raises(ValueError, match="no variation and no jump"): + rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, t=const) + with pytest.raises(ValueError, match="no variation and no jump"): + rdbwselect(y, x, fuzzy=const) + + def test_column_vector_t_accepted(self): + y, x, t = self._yxt() + a = rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, t=t) + b = rdrobust_fit(y, x, 0.0, 0.5, 0.5, 0.5, 0.5, t=t.reshape(-1, 1)) + assert a.tau_bc == b.tau_bc and a.se_T_rb == b.se_T_rb From e44dcde0244c166a650d6579dffb89d789902e78 Mon Sep 17 00:00:00 2001 From: igerber Date: Mon, 13 Jul 2026 14:26:57 -0400 Subject: [PATCH 2/5] docs: fix stale rdbwselect_sharp reference in benchmarks R README The port entry point was renamed to rdbwselect when fuzzy support landed; this internal README reference was the one site the rename sweep missed. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4 --- benchmarks/R/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/R/README.md b/benchmarks/R/README.md index a08ee557..341ed04e 100644 --- a/benchmarks/R/README.md +++ b/benchmarks/R/README.md @@ -137,7 +137,7 @@ flatten misaligns when reshaped row-major. `benchmarks/R/generate_rdrobust_golden.R` produces `benchmarks/data/rdrobust_golden.json`, consumed by `tests/test_rdrobust_port.py` to verify that -`diff_diff._rdrobust_port.rdbwselect_sharp` matches R `rdrobust::rdbwselect` +`diff_diff._rdrobust_port.rdbwselect` (sharp and fuzzy RD bandwidth paths) matches R `rdrobust::rdbwselect` (Calonico, Cattaneo, Farrell & Titiunik) across all 10 bandwidth selectors at rtol ≤ 1e-9. From ea3eb1336bda9ce6a41761ff39e0d717357337e2 Mon Sep 17 00:00:00 2001 From: igerber Date: Mon, 13 Jul 2026 14:32:24 -0400 Subject: [PATCH 3/5] docs: scope the benchmarks README golden descriptions precisely rdrobust_golden.json is the frozen 17-config SHARP bandwidth fixture; fuzzy bandwidth/estimation parity lives in rdrobust_estimates_golden.json. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4 --- benchmarks/R/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/benchmarks/R/README.md b/benchmarks/R/README.md index 341ed04e..96e86eb0 100644 --- a/benchmarks/R/README.md +++ b/benchmarks/R/README.md @@ -137,9 +137,14 @@ flatten misaligns when reshaped row-major. `benchmarks/R/generate_rdrobust_golden.R` produces `benchmarks/data/rdrobust_golden.json`, consumed by `tests/test_rdrobust_port.py` to verify that -`diff_diff._rdrobust_port.rdbwselect` (sharp and fuzzy RD bandwidth paths) matches R `rdrobust::rdbwselect` -(Calonico, Cattaneo, Farrell & Titiunik) across all 10 bandwidth selectors at -rtol ≤ 1e-9. +`diff_diff._rdrobust_port.rdbwselect` matches R `rdrobust::rdbwselect` +(Calonico, Cattaneo, Farrell & Titiunik) on SHARP bandwidth selection +across all 10 selectors at rtol ≤ 1e-9 (17 configs; this fixture predates +fuzzy support and is deliberately never regenerated). Fuzzy bandwidth and +estimation parity lives in `benchmarks/data/rdrobust_estimates_golden.json` +(generator `generate_rdrobust_estimates_golden.R`), pinned by +`tests/test_rdrobust_port.py::TestFuzzyPortGoldenParity` and +`tests/test_rdd_parity.py`. ## Version pin From 500ddb7c2f3e04fb7fffc0364ea4985e28a31750 Mon Sep 17 00:00:00 2001 From: igerber Date: Mon, 13 Jul 2026 14:38:05 -0400 Subject: [PATCH 4/5] docs: qualify complier-LATE prose with the binary-take-up condition everywhere The estimand field and fit docstring were already conditional; the module docstring intro, API page intro, and llms-full description/example now carry the same binary-take-up-under-monotonicity qualifier. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4 --- diff_diff/guides/llms-full.txt | 4 ++-- diff_diff/rdd.py | 8 +++++--- docs/api/regression_discontinuity.rst | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/diff_diff/guides/llms-full.txt b/diff_diff/guides/llms-full.txt index 7743bd85..11a3753a 100644 --- a/diff_diff/guides/llms-full.txt +++ b/diff_diff/guides/llms-full.txt @@ -826,7 +826,7 @@ es = est.fit(data_mp, outcome_col='y', unit_col='unit', ### RegressionDiscontinuity -Regression discontinuity estimator - sharp and fuzzy (Calonico, Cattaneo & Titiunik 2014), parity-targeting R rdrobust 4.0.0. SHARP (default): treatment is assigned by a known threshold of an observed running variable (`running >= cutoff`; units exactly at the cutoff are treated); no treatment column. FUZZY: pass the OBSERVED take-up column via `fit(..., treatment_col=...)` (R's `fuzzy=`) - the estimand becomes the local Wald ratio (complier LATE at the cutoff under monotonicity) with a linearized bias correction, and the results gain a full `first_stage*` three-row block. Point estimation via kernel-weighted local polynomials on each side; data-driven MSE/CER-optimal bandwidths (all 10 rdrobust selectors; fuzzy selects on the ratio objective by default, with a sharp-on-Y switch under one-sided perfect compliance or `sharpbw=True`); robust bias-corrected inference. Cross-sectional - no panel/time dimension. +Regression discontinuity estimator - sharp and fuzzy (Calonico, Cattaneo & Titiunik 2014), parity-targeting R rdrobust 4.0.0. SHARP (default): treatment is assigned by a known threshold of an observed running variable (`running >= cutoff`; units exactly at the cutoff are treated); no treatment column. FUZZY: pass the OBSERVED take-up column via `fit(..., treatment_col=...)` (R's `fuzzy=`) - the estimand becomes the local Wald ratio (complier LATE at the cutoff for BINARY take-up under monotonicity; ratio-of-jumps otherwise - the `estimand` field says which) with a linearized bias correction, and the results gain a full `first_stage*` three-row block. Point estimation via kernel-weighted local polynomials on each side; data-driven MSE/CER-optimal bandwidths (all 10 rdrobust selectors; fuzzy selects on the ratio objective by default, with a sharp-on-Y switch under one-sided perfect compliance or `sharpbw=True`); robust bias-corrected inference. Cross-sectional - no panel/time dimension. ```python RegressionDiscontinuity( @@ -874,7 +874,7 @@ results.h_left, results.b_left # selected bandwidths print(results.summary()) # three-row Conventional / Bias-Corrected / Robust table, as in rdrobust fuzzy = rd.fit(df, "y", "score", treatment_col="takeup") # fuzzy RD -fuzzy.att # complier LATE at the cutoff (linearized bias-corrected ratio, robust row) +fuzzy.att # linearized bias-corrected local Wald ratio, robust row (complier LATE for binary take-up) fuzzy.first_stage # take-up jump (bias-corrected; full three-row first_stage* mirror available) fuzzy.estimand # "fuzzy (LATE for compliers at the cutoff)" (binary take-up) / "fuzzy (local Wald ratio at the cutoff; non-binary take-up)" / "sharp (ATE at the cutoff)" ``` diff --git a/diff_diff/rdd.py b/diff_diff/rdd.py index efd8d0e1..6e628c09 100644 --- a/diff_diff/rdd.py +++ b/diff_diff/rdd.py @@ -9,9 +9,11 @@ ``fit(..., treatment_col=...)`` with the OBSERVED take-up column): crossing the cutoff shifts take-up rather than determining it, and the estimand is the local Wald ratio - the outcome jump divided by the -take-up jump - which under monotonicity is the LATE for compliers at the -cutoff. Both designs use kernel-weighted polynomial regressions on each -side with data-driven MSE/CER-optimal bandwidths and robust +take-up jump - which for BINARY take-up under monotonicity is the LATE +for compliers at the cutoff (non-binary take-up keeps the ratio-of-jumps +reading; the ``estimand`` results field says which applies). Both designs +use kernel-weighted polynomial regressions on each side with data-driven +MSE/CER-optimal bandwidths and robust bias-corrected (RBC) inference; the fuzzy bias correction is the linearization of the ratio (not per-component), matching CCT 2014 Section 3.2 and rdrobust exactly. diff --git a/docs/api/regression_discontinuity.rst b/docs/api/regression_discontinuity.rst index 6a8ef715..8b667d65 100644 --- a/docs/api/regression_discontinuity.rst +++ b/docs/api/regression_discontinuity.rst @@ -10,8 +10,10 @@ cutoff are treated, matching rdrobust). The effect is the jump in the conditional expectation of the outcome at the cutoff. **Fuzzy** (pass the observed take-up column via ``fit(..., treatment_col=...)``): crossing the cutoff shifts take-up instead of determining it, and the estimand is -the local Wald ratio - under monotonicity, the LATE for compliers at the -cutoff - with the first stage exposed as a full ``first_stage*`` block. +the local Wald ratio - for binary take-up under monotonicity, the LATE +for compliers at the cutoff; for non-binary take-up, the ratio of jumps +(the ``estimand`` field says which) - with the first stage exposed as a +full ``first_stage*`` block. Both designs use kernel-weighted local polynomials on each side with data-driven MSE/CER-optimal bandwidths (all 10 rdrobust selectors) and robust bias-corrected inference per Calonico, Cattaneo & Titiunik (2014). From 098515469034dae9e3a5df18f70313167054be30 Mon Sep 17 00:00:00 2001 From: igerber Date: Mon, 13 Jul 2026 14:44:01 -0400 Subject: [PATCH 5/5] docs: finish the binary-take-up qualifier sweep on complier-LATE prose CHANGELOG entry, class docstring, and doctest comment were the last unqualified sites (repo-wide grep now clean). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4 --- CHANGELOG.md | 3 ++- diff_diff/rdd.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 521082da..c208b387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 applying to selected bandwidths and the unconditional N<20 full-range fallback), and the three-row Conventional / Bias-Corrected / Robust output. **Fuzzy designs** via `fit(..., treatment_col=...)` (R's `fuzzy=`): the estimand is the local Wald - ratio (complier LATE at the cutoff under monotonicity) with the LINEARIZED + ratio (complier LATE at the cutoff for binary take-up under monotonicity; + ratio-of-jumps otherwise, with a data-dependent `estimand` label) with the LINEARIZED bias correction and delta-method variance of rdrobust (T stacked as a second response column; Y-T covariance via the `res @ s_Y` collapse); bandwidths select on the fuzzy-ratio objective by default with R's exact `sharpbw`/one-sided diff --git a/diff_diff/rdd.py b/diff_diff/rdd.py index 6e628c09..374e54a8 100644 --- a/diff_diff/rdd.py +++ b/diff_diff/rdd.py @@ -427,8 +427,9 @@ class RegressionDiscontinuity: a known cutoff (``running >= cutoff`` treated, matching rdrobust: units exactly at the cutoff are treated). FUZZY: pass the observed take-up column via ``fit(..., treatment_col=...)`` - the estimand - becomes the local Wald ratio (complier LATE at the cutoff under - monotonicity) and the results gain a first-stage block. Point + becomes the local Wald ratio (complier LATE at the cutoff for binary + take-up under monotonicity; the ``estimand`` results field says which + reading applies) and the results gain a first-stage block. Point estimation uses kernel-weighted local polynomials of order ``p`` on each side; inference is robust bias-corrected per Calonico, Cattaneo & Titiunik (2014). Defaults reproduce ``rdrobust(y, x)`` / @@ -498,7 +499,7 @@ class RegressionDiscontinuity: >>> results = rd.fit(df, outcome_col="y", running_col="x") >>> results.att, results.conf_int # robust bias-corrected inference >>> fuzzy = rd.fit(df, "y", "x", treatment_col="takeup") # fuzzy RD - >>> fuzzy.att, fuzzy.first_stage # complier LATE + take-up jump + >>> fuzzy.att, fuzzy.first_stage # local Wald ratio + take-up jump """ def __init__(