Regression Discontinuity#
Regression discontinuity estimation - sharp and fuzzy, with optional
covariate adjustment - and 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(..., takeup=...)): crossing
the cutoff shifts take-up instead of determining it, and the estimand is
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).
Covariate adjustment (fit(..., covariates=[...]); R’s covs=,
per Calonico, Cattaneo, Farrell & Titiunik 2019): additive
common-coefficient adjustment that leaves the estimand UNCHANGED -
unlike the DiD estimators’ conditional-parallel-trends role, RD
covariates buy precision only, and require covariate balance at the
cutoff (testable by fitting each covariate as the outcome). Bandwidths
are covariate-aware; collinear covariates are dropped with a warning
naming them (covs_drop=True, R’s default).
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 - 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, fuzzy, and covariate-adjusted
designs with the nearest-neighbor variance estimator (rdrobust’s
default), plus the RDPlot and RDDensityTest diagnostics
below. Cluster-robust variance, weights, kink estimands,
weak-IV-robust fuzzy inference, and a packaged covariate-balance
helper are documented follow-ups - see the methodology registry for
the full deviations and seams list.
RD plots (RDPlot; Calonico, Cattaneo & Titiunik 2015, parity with
R’s rdplot()): the standard exploratory companion - per-side global
polynomial fits plus binned local means with a data-driven number of bins
(all 8 binselect selectors: evenly/quantile spaced, IMSE-optimal or
mimicking-variance, spacings or polynomial-regression variance estimators),
implied-scale/WIMSE-weight reporting, optional covariate adjustment, and an
optional matplotlib rendering via RDPlotResult.plot() (matplotlib is
not a dependency; the numbers are the parity surface).
RegressionDiscontinuity#
- class diff_diff.RegressionDiscontinuity[source]
Bases:
BaseEstimatorRegression discontinuity estimator - sharp and fuzzy, with optional covariate adjustment (rdrobust 4.0.0 parity).
SHARP (default): treatment is defined by the running variable crossing a known cutoff (
running >= cutofftreated, matching rdrobust: units exactly at the cutoff are treated). FUZZY: pass the observed take-up column viafit(..., takeup=...)- the estimand becomes the local Wald ratio (complier LATE at the cutoff for binary take-up under monotonicity; theestimandresults field says which reading applies) and the results gain a first-stage block. COVARIATE ADJUSTMENT: passfit(..., covariates=[...])(R’scovs=) for the CCFT 2019 additive common-coefficient adjustment - the estimand is UNCHANGED (precision only; requires covariate balance at the cutoff, see the module docstring), bandwidths become covariate-aware, and collinear columns are dropped with a warning undercovs_drop=True. Point estimation uses kernel-weighted local polynomials of orderpon each side; inference is robust bias-corrected per Calonico, Cattaneo & Titiunik (2014). Defaults reproducerdrobust(y, x)/rdrobust(y, x, fuzzy=t)/rdrobust(y, x, covs=Z):p=1,q=2, triangular kernel,bwselect="mserd", nearest-neighbor variance with 3 matches,masspoints="adjust",covs_drop=True.- Parameters:
cutoff (float, default 0.0) – The known threshold
cof the running variable.p (int, default 1) – Local-polynomial order for point estimation; integer in 0..20 (mirroring rdrobust’s accepted surface;
p=0is the local-constant fit).q (int or None, default None) – Order for the bias regression; an explicit
qmust satisfyp < q <= 20.Noneresolves top + 1WITHOUT re-validation, exactly as R does (rdrobust.R:53-57) - sop=20with the defaultqyieldsq=21in both implementations while an explicitq=21is rejected.kernel (str, default "triangular") – “triangular”, “epanechnikov”, or “uniform” (R spellings “tri”/”epa”/”uni” accepted).
bwselect (str, default "mserd") – Data-driven bandwidth selector; one of the 10 rdrobust options (mserd, msetwo, msesum, msecomb1, msecomb2, cerrd, certwo, cersum, cercomb1, cercomb2). Ignored when
his supplied.h (float or None) – Manual main / bias bandwidths (both sides).
halone impliesb = h;hwithrhoimpliesb = h/rho(overriding a suppliedb, as in R);bwithouthis ignored with a warning (R ignores it silently - documented deviation).b (float or None) – Manual main / bias bandwidths (both sides).
halone impliesb = h;hwithrhoimpliesb = h/rho(overriding a suppliedb, as in R);bwithouthis ignored with a warning (R ignores it silently - documented deviation).rho (float or None) – Bandwidth ratio
h/b. Withouth, applies to the SELECTED bandwidths (b = h_selected/rho), mirroring rdrobust.vcov_type (str, default "nn") – Variance estimator. Only “nn” (same-side nearest-neighbor, rdrobust’s default) is implemented in this release; “hc0”-“hc3” and cluster modes raise
NotImplementedError.nnmatch (int, default 3) – Minimum number of nearest neighbors for the NN variance.
masspoints (str, default "adjust") – Mass-point handling: “adjust” (rdrobust default), “check”, “off”.
bwcheck (int or None, default None) – Minimum unique support points forced inside the bandwidth window.
bwrestrict (bool, default True) – Clamp bandwidths to the running variable’s observed range.
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(..., takeup=...)): 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). Never drops covariates from selection - withcovariatesit selects on the covariate-adjusted sharp objective, as in R.covs_drop (bool, default True) – Covariate-adjusted fits only (
fit(..., covariates=[...])): when True (R’s default), redundant (collinear) covariate columns are dropped with a warning naming them before fitting, and the covariate projection uses a pseudo-inverse; when False the solve is strict and collinear covariates raise a clear error. Withoutcovariatesthe flag has no effect and setting it to False emits a warning (same pattern assharpbwon sharp fits).alpha (float, default 0.05) – Significance level (rdrobust
level = 100*(1-alpha)).
Examples
>>> rd = RegressionDiscontinuity(cutoff=0.0) >>> results = rd.fit(df, outcome="y", running="x") >>> results.att, results.conf_int # robust bias-corrected inference >>> fuzzy = rd.fit(df, "y", "x", takeup="takeup") # fuzzy RD >>> fuzzy.att, fuzzy.first_stage # local Wald ratio + take-up jump
- __init__(cutoff=0.0, p=1, q=None, kernel='triangular', bwselect='mserd', h=None, b=None, rho=None, vcov_type='nn', nnmatch=3, masspoints='adjust', bwcheck=None, bwrestrict=True, scaleregul=1.0, sharpbw=False, covs_drop=True, alpha=0.05)[source]
- fit(data, outcome=<not supplied>, running=<not supplied>, takeup=<not supplied>, covariates=None, outcome_col=<not supplied>, running_col=<not supplied>, treatment_col=<not supplied>)[source]
Estimate the RD effect at the cutoff (sharp or fuzzy, optionally covariate-adjusted).
- Parameters:
data (pd.DataFrame) – Cross-sectional data.
outcome (str) – Column names of the outcome and the running variable.
running (str) – Column names of the outcome and the running variable.
takeup (str or None, default None) –
None(sharp design): treatment is derived asrunning >= 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’sfuzzy=), the estimand becomes the local Wald ratio, and the results gain thefirst_stage*block. Theestimandlabel 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).covariates (list of str or None, default None) – Column names of pre-determined covariates for the additive common-coefficient adjustment of CCFT (2019) (R’s
covs=). The estimand is UNCHANGED - unlike the DiD estimators’ conditional-parallel-trends role, RD covariates buy precision only, and require covariate BALANCE at the cutoff (zero RD effect on each covariate; testable by fitting each covariate as the outcome - imbalanced covariates make the adjusted estimator inconsistent). Continuous, discrete, or mixed columns are accepted; covariates propagate into bandwidth selection (covariate-aware, as in R). Collinear columns are dropped with a warning undercovs_drop=True; see thecovariates*results fields for the echo and the fitted projection coefficients.outcome_col (str, optional) – Deprecated aliases for
outcome/running/takeup(rows M-040..M-042); each warns withFutureWarningand will be removed in 4.0.running_col (str, optional) – Deprecated aliases for
outcome/running/takeup(rows M-040..M-042); each warns withFutureWarningand will be removed in 4.0.treatment_col (str, optional) – Deprecated aliases for
outcome/running/takeup(rows M-040..M-042); each warns withFutureWarningand will be removed in 4.0.
- Return type:
- get_params(deep=True)
Get estimator parameters (sklearn-compatible).
- Parameters:
deep (bool, default True) – Accepted for sklearn compatibility (
sklearn.base.clonecallsget_params(deep=False)) and ignored: no diff-diff estimator nests another estimator.- Returns:
Estimator parameters suitable for passing to
__init__. Keys follow the__init__signature order. Where a class stores a parameter’s raw value under a different attribute (_PARAM_ATTR_ALIASES), the raw value is returned so thattype(est)(**est.get_params())reconstructs the same configuration.- Return type:
Dict[str, Any]
- set_params(**params)
Set estimator parameters (sklearn-compatible, transactional).
Validates the merged configuration by constructing a throwaway instance of
type(self)- so a rejected call raises the same error__init__would and leaves this estimator unchanged - then adopts the probe’s parameter attributes and the class’s declared derived-config attributes. Fitted state is never touched.- Parameters:
**params (Any) – Estimator parameters. Unknown names raise
ValueErrorbefore any validation or mutation.self (TSelf)
**params
- Return type:
self
RegressionDiscontinuityResults#
- class diff_diff.RegressionDiscontinuityResults[source]
Bases:
BaseResultsResults of a regression discontinuity fit (sharp or fuzzy; the
estimandfield names which one, andfirst_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:attis the bias-corrected point estimate andconf_intis centered on it (see module docstring for the binding rationale and the deviation from rdrobust’s printed headline). The conventional row is exposed as explicit*_conventionalfields. The (rarely used) middle “Bias-Corrected” row shares its coefficient withatt(both aretau_bc) and its standard error withse_conventional- only its inference triple carries the*_bias_correctedsuffix (t_stat_bias_corrected,p_value_bias_corrected,conf_int_bias_corrected); there are deliberately no redundantatt_bias_corrected/se_bias_correctedfields. Together the three rows mirror rdrobust’s output exactly.- att: float
- se: float
- t_stat: float
- p_value: float
- alpha: float
- att_conventional: float
- se_conventional: float
- t_stat_conventional: float
- p_value_conventional: float
- t_stat_bias_corrected: float
- p_value_bias_corrected: float
- se_robust: float
- h_left: float
- h_right: float
- b_left: float
- b_right: float
- n_obs: int
- n_left: int
- n_right: int
- n_h_left: int
- n_h_right: int
- n_b_left: int
- n_b_right: int
- n_unique_left: int
- n_unique_right: int
- n_dropped: int
- cutoff: float
- p: int
- q: int
- kernel: str
- bwselect: str
- vcov_type: str
- nnmatch: int
- masspoints: str
- bwrestrict: bool
- scaleregul: float
- estimand: str
- sharpbw: bool
- covs_drop: bool
- beta_p_left: ndarray = None
- beta_p_right: ndarray = None
- property treatment_col: Any
Deprecated alias for
takeup(removed in 4.0).
- __setstate__(state)[source]
Migrate pickles created before the
treatment_col->takeuprename (row M-094): rewrite the key on load so both the new field and the deprecated alias work on old pickles.
- print_summary()[source]
- Return type:
None
- to_dict()[source]
Flat scalar dict; confidence intervals split into lower/upper.
- __init__(att, se, t_stat, p_value, conf_int, alpha, att_conventional, se_conventional, t_stat_conventional, p_value_conventional, conf_int_conventional, t_stat_bias_corrected, p_value_bias_corrected, conf_int_bias_corrected, se_robust, h_left, h_right, b_left, b_right, n_obs, n_left, n_right, n_h_left, n_h_right, n_b_left, n_b_right, n_unique_left, n_unique_right, n_dropped, cutoff, p, q, kernel, bwselect, vcov_type, nnmatch, masspoints, bwcheck, bwrestrict, scaleregul, h_input, b_input, rho_input, estimand, sharpbw, takeup, covs_drop, first_stage=None, first_stage_se=None, first_stage_t_stat=None, first_stage_p_value=None, first_stage_conf_int=None, first_stage_conventional=None, first_stage_se_conventional=None, first_stage_t_stat_conventional=None, first_stage_p_value_conventional=None, first_stage_conf_int_conventional=None, first_stage_t_stat_bias_corrected=None, first_stage_p_value_bias_corrected=None, first_stage_conf_int_bias_corrected=None, covariates=None, covariates_dropped=None, covariate_coefficients=None, first_stage_covariate_coefficients=None, beta_p_left=None, beta_p_right=None, beta_t_p_left=None, beta_t_p_right=None)
- Parameters:
att (float)
se (float)
t_stat (float)
p_value (float)
alpha (float)
att_conventional (float)
se_conventional (float)
t_stat_conventional (float)
p_value_conventional (float)
t_stat_bias_corrected (float)
p_value_bias_corrected (float)
se_robust (float)
h_left (float)
h_right (float)
b_left (float)
b_right (float)
n_obs (int)
n_left (int)
n_right (int)
n_h_left (int)
n_h_right (int)
n_b_left (int)
n_b_right (int)
n_unique_left (int)
n_unique_right (int)
n_dropped (int)
cutoff (float)
p (int)
q (int)
kernel (str)
bwselect (str)
vcov_type (str)
nnmatch (int)
masspoints (str)
bwcheck (int | None)
bwrestrict (bool)
scaleregul (float)
h_input (float | None)
b_input (float | None)
rho_input (float | None)
estimand (str)
sharpbw (bool)
takeup (str | None)
covs_drop (bool)
first_stage (float | None)
first_stage_se (float | None)
first_stage_t_stat (float | None)
first_stage_p_value (float | None)
first_stage_conventional (float | None)
first_stage_se_conventional (float | None)
first_stage_t_stat_conventional (float | None)
first_stage_p_value_conventional (float | None)
first_stage_conf_int_conventional (Tuple[float, float] | None)
first_stage_t_stat_bias_corrected (float | None)
first_stage_p_value_bias_corrected (float | None)
first_stage_conf_int_bias_corrected (Tuple[float, float] | None)
first_stage_covariate_coefficients (Dict[str, float] | None)
beta_p_left (ndarray)
beta_p_right (ndarray)
beta_t_p_left (ndarray | None)
beta_t_p_right (ndarray | None)
- Return type:
None
RDPlot#
- class diff_diff.RDPlot[source]
Bases:
BaseEstimatorData-driven RD plot builder (CCT 2015; rdrobust 4.0.0
rdplot()parity).Parameters mirror R’s
rdplot()and keep its defaults - includingkernel="uniform"andp=4, which deliberately differ fromRegressionDiscontinuity’s estimation defaults (triangular, p=1): the global fit is a descriptive overlay, not the local RD estimator.- Parameters:
cutoff (float, default 0.0) – Threshold in the running variable.
p (int, default 4) – Order of the global polynomial fits (R accepts any integer >= 0).
nbins (int or (int, int), optional) – Manual number of bins per side; overrides the data-driven selector. Any positive count is accepted (R permissiveness); note that very large bin counts allocate proportional jump grids and, with covariates, a dense per-side bin-dummy matrix (documented performance seam).
binselect (str, default "esmv") – One of
es, espr, esmv, esmvpr, qs, qspr, qsmv, qsmvpr- partition scheme (evenly/quantile spaced) x target (IMSE-optimal / mimicking variance) x variance estimator (spacings / polynomial regression). The spacings variants assume a continuously distributed OUTCOME (CCT 2015 Theorems 3-4); heavy outcome ties trigger a warning recommending the*prsibling. QS cutpoints follow R:quantiletype-7 interpolation, not the paper’s inf-form empirical inverse (documented deviation-from-paper inherited from rdrobust).scale (float or (float, float), optional) – Multiplies the selected number of bins (undersmoothing knob). A fractional product is resolved with the ceiling of CCT 2015 Equation 2. Deviation from R: rdrobust 4.0.0 crashes on fractional products (vector-indexing accident) and rejects a length-2 scale outright on R >= 4.2 (vectorized-if error); both surfaces work here.
kernel (str, default "uniform") – Weighting for the global polynomial fit within
h.h (float or (float, float), optional) – Window for the global fit; default is each side’s full range.
support ((float, float), optional) – Extended support for bin construction; only WIDENS the observed range (R semantics).
masspoints (str, default "adjust") –
"check"warns when either side has >= 20% duplicate running values;"adjust"additionally switches a spacingsbinselectto its polynomial-regression sibling;"off"disables detection.ci (float, optional) – Confidence level (percent) for the per-bin intervals drawn by
RDPlotResult.plot(). CI columns are ALWAYS computed (at 95 whenciis None, matching R);cionly sets the level and turns the error bars on.covs_drop (bool, default True) – Drop redundant covariates (R’s pipeline: stable name-length sort + LINPACK-style QR check) with a warning naming the dropped columns;
Falseraises a deterministic error on collinear covariates.
- __init__(cutoff=0.0, p=4, nbins=None, binselect='esmv', scale=None, kernel='uniform', h=None, support=None, masspoints='adjust', ci=None, covs_drop=True)[source]
- fit(data, outcome=<not supplied>, running=<not supplied>, covariates=None, outcome_col=<not supplied>, running_col=<not supplied>)[source]
Build the RD plot quantities.
- Parameters:
data (pd.DataFrame) – Cross-sectional data.
outcome (str) – Column names of the outcome and the running variable.
running (str) – Column names of the outcome and the running variable.
covariates (list of str, optional) – Covariate-adjusted plot per rdplot’s
covs=(global fit partials the covariates out with a pooled-across-sides gamma; bin means use the fitted values of per-side regressions of the outcome on covariates and bin indicators, R’slm(y ~ z + factor(bin))withcovs_eval="mean"). Note R’s caveat: covariate adjustment is meant for plotting alongside covariate-adjusted rdrobust ESTIMATES; the adjusted global fit may not be visually compatible with unadjusted binned means.covariate_coefficientsis keyed in R’s processing order: name-length-sorted undercovs_drop=True(the sort decides which of a collinear set survives), the user-given order undercovs_drop=False(R sorts only inside its drop pipeline; same contract asRegressionDiscontinuity). Per-name values are identical either way on full-rank covariates.outcome_col (str, optional) – Deprecated aliases for
outcome/running(rows M-088/M-089); each warns withFutureWarningand will be removed in 4.0.running_col (str, optional) – Deprecated aliases for
outcome/running(rows M-088/M-089); each warns withFutureWarningand will be removed in 4.0.
- Return type:
- get_params(deep=True)
Get estimator parameters (sklearn-compatible).
- Parameters:
deep (bool, default True) – Accepted for sklearn compatibility (
sklearn.base.clonecallsget_params(deep=False)) and ignored: no diff-diff estimator nests another estimator.- Returns:
Estimator parameters suitable for passing to
__init__. Keys follow the__init__signature order. Where a class stores a parameter’s raw value under a different attribute (_PARAM_ATTR_ALIASES), the raw value is returned so thattype(est)(**est.get_params())reconstructs the same configuration.- Return type:
Dict[str, Any]
- set_params(**params)
Set estimator parameters (sklearn-compatible, transactional).
Validates the merged configuration by constructing a throwaway instance of
type(self)- so a rejected call raises the same error__init__would and leaves this estimator unchanged - then adopts the probe’s parameter attributes and the class’s declared derived-config attributes. Fitted state is never touched.- Parameters:
**params (Any) – Estimator parameters. Unknown names raise
ValueErrorbefore any validation or mutation.self (TSelf)
**params
- Return type:
self
RDPlotResult#
- class diff_diff.RDPlotResult[source]
Bases:
DiagnosticQuantities of a data-driven RD plot (field names follow R’s rdplot return list;
vars_binscolumns carry R’srdplot_*names).Jis always integral: a fractionalscale * Jproduct takes the ceiling per CCT 2015 Equation 2 (see the Deviation note inRDPlot- R 4.0.0 crashes on that surface instead).vars_binscontains only NON-EMPTY bins (R’stapplysemantics) whilebin_avg/bin_medsummarize ALL bin lengths of the partition. CI columns are always computed (atci_level);ci_requestedrecords whetherci=was passed and is the default for drawing error bars inplot().- coef: DataFrame
- vars_bins: DataFrame
- vars_poly: DataFrame
- p: int
- cutoff: float
- binselect: str
- kernel_type: str
- ci_level: float
- ci_requested: bool
- property wimse_variance_weight: Tuple[float, float]
Implied WIMSE variance weights
1/(1 + rscale^3)per side (CCT 2015 Supplement S.1; printed by R’ssummary.rdplot).
- property wimse_bias_weight: Tuple[float, float]
Implied WIMSE bias weights
rscale^3/(1 + rscale^3)per side.
- summary()[source]
Formatted summary mirroring R’s
summary.rdplotlayout (rdplot.R:610-654), including the implied scale and WIMSE weights.- Return type:
- plot(ax=None, title=None, xlabel=None, ylabel=None, dots_color=None, line_color=None, show_ci=None)[source]
Render the RD plot (binned means, global polynomial curves, cutoff line; error bars when CIs were requested at fit time or forced via
show_ci=True). Requires matplotlib (an optional dependency).Returns the matplotlib axes. Rendering is cosmetic - the parity surface of this port is the NUMBERS on the result object.
- __init__(coef, vars_bins, vars_poly, J, J_IMSE, J_MV, scale, rscale, bin_avg, bin_med, p, cutoff, h, N, N_h, binselect, kernel_type, ci_level, ci_requested, covariate_coefficients=None, covariates_dropped=None)
Manipulation testing (RDDensityTest; Cattaneo, Jansson & Ma 2020,
parity with R’s rddensity() 3.0): the density-discontinuity
(McCrary-style) manipulation test - the running variable’s density is
estimated on each side of the cutoff with the CJM boundary-adaptive local
polynomial density estimator and the difference is t-tested with robust
bias-corrected inference. Unrestricted and restricted models, jackknife
(default) and plug-in variances, the full each/diff/sum/comb
data-driven bandwidth menu, and R-parity mass-point adjustment via the
RD-family masspoints= surface.
RDDensityTest#
- class diff_diff.RDDensityTest[source]
Bases:
BaseEstimatorManipulation testing via local polynomial density estimation (rddensity 3.0 parity).
Tests the null of NO discontinuity in the running variable’s density at the cutoff (McCrary 2008’s question) with the boundary-adaptive local polynomial density estimator of Cattaneo, Jansson & Ma (2020) and robust bias-corrected inference: density estimators of order
q = p + 1are evaluated at bandwidths selected as MSE-optimal for the order-pestimator. Defaults reproduce Rrddensity(X):p=2, triangular kernel, jackknife variance, unrestricted model,bwselect="comb", mass-point adjustment on.Assumptions (CJM 2020 Assumption 1, held separately on each side of the cutoff): the observations are an i.i.d. random sample, and the running variable’s distribution function is smooth with a POSITIVE density near the cutoff on each side. The jackknife/plugin variances assume cross-observation independence - clustered, survey-weighted, or repeated-observation dependence is NOT supported by the reviewed theory and there is no
cluster=surface here. Interpret non-rejection as “no density discontinuity detected”, which supports but never proves the RD continuity assumptions; a significant test is evidence of sorting around the cutoff.- Parameters:
cutoff (float, default 0.0) – The threshold
cin the support of the running variable.p (int, default 2) – Local polynomial order for the density point estimators (1..7, R’s accepted surface).
q (int or None, default None) – Order for the bias-corrected estimators;
Noneresolves top + 1at fit time (R uses aq=0sentinel). An explicitqmust satisfyq >= p;q == pgives the conventional (uncorrected) test, which OVER-REJECTS at data-driven MSE-optimal bandwidths (CJM 2020 Section 4) - valid conventional inference requires a manually undersmoothedh, the caller’s responsibility (a fit-time warning fires whenhis data-driven).fitselect (str, default "unrestricted") –
"unrestricted": separate model on each side."restricted": equal distribution function and higher-order derivatives across the cutoff (only the density may jump); requires equal bandwidths and forbidsbwselect="each".kernel (str, default "triangular") – “triangular”, “epanechnikov”, or “uniform” (R spellings “tri”/”epa”/”uni” accepted).
vcov_type (str, default "jackknife") – Variance estimator: “jackknife” (R’s default; the SA Section 5.2 leave-one-out construction, window-restricted as in R) or “plugin” (asymptotic plug-in).
h (float or (float, float) or None, default None) – Manual bandwidth(s). A scalar applies to both sides; a pair is
(h_left, h_right).Noneselects data-driven bandwidths.bwselect (str, default "comb") – Bandwidth selection: “each” (per-side MSE), “diff” (MSE of the difference), “sum” (MSE of the sum), “comb” (median of each/diff/sum per side for the unrestricted model; min of diff/sum for the restricted model). Ignored when
his supplied.masspoints (str, default "adjust") – Mass-point handling: “adjust” (R
massPoints=TRUE- the EDF is computed on unique values and replicated back, with a warning), “check” (detect repeated values and warn WITHOUT adjusting - estimation matches RmassPoints=FALSE), “off” (no detection). String surface matching the RD-familymasspointsdomain (documented Deviation from R’s boolean).regularize (bool, default True) – Local sample-size regularization of the data-driven bandwidths.
n_local_min (real or None, default None -> 20 + p + 1) – Minimum observations in each local neighborhood (final-stage floor).
ceil()is applied as in R;ceil(value) >= 0required, 0 disables the gate. Must be finite (Deviation from R, which accepts Inf).n_unique_min (real or None, default None -> 20 + p + 1) – Minimum unique observations in each local neighborhood; same contract as
n_local_min.report_all (bool, default False) – Also compute and report the conventional (order-
p) test alongside the robust one (R’sall=TRUE). The conventional test’s inference carries the same over-rejection caveat asq == pat data-driven bandwidths (seeqabove).
Examples
>>> test = RDDensityTest(cutoff=0.0) >>> result = test.fit(df, running="score") >>> result.t_stat, result.p_value # robust bias-corrected test
- __init__(cutoff=0.0, p=2, q=None, fitselect='unrestricted', kernel='triangular', vcov_type='jackknife', h=None, bwselect='comb', masspoints='adjust', regularize=True, n_local_min=None, n_unique_min=None, report_all=False)[source]
- fit(data, running)[source]
Run the manipulation test on
data[running].- Parameters:
data (pd.DataFrame) – Input data.
running (str) – Column name of the running variable.
- Return type:
- get_params(deep=True)
Get estimator parameters (sklearn-compatible).
- Parameters:
deep (bool, default True) – Accepted for sklearn compatibility (
sklearn.base.clonecallsget_params(deep=False)) and ignored: no diff-diff estimator nests another estimator.- Returns:
Estimator parameters suitable for passing to
__init__. Keys follow the__init__signature order. Where a class stores a parameter’s raw value under a different attribute (_PARAM_ATTR_ALIASES), the raw value is returned so thattype(est)(**est.get_params())reconstructs the same configuration.- Return type:
Dict[str, Any]
- set_params(**params)
Set estimator parameters (sklearn-compatible, transactional).
Validates the merged configuration by constructing a throwaway instance of
type(self)- so a rejected call raises the same error__init__would and leaves this estimator unchanged - then adopts the probe’s parameter attributes and the class’s declared derived-config attributes. Fitted state is never touched.- Parameters:
**params (Any) – Estimator parameters. Unknown names raise
ValueErrorbefore any validation or mutation.self (TSelf)
**params
- Return type:
self
RDDensityTestResult#
- class diff_diff.RDDensityTestResult[source]
Bases:
DiagnosticResults of the CJM 2020 manipulation (density-discontinuity) test.
The headline
t_stat/p_valueare the ROBUST BIAS-CORRECTED test (order-qdensity estimators at order-p-optimal bandwidths), with the standard error selected byvcov_type- whenq > p; a fit withq == pmakes the headline the CONVENTIONAL (uncorrected) test, which over-rejects at data-driven bandwidths (see the estimator class docstring). The*_conventionalfields carry the same caveat. Thef_*fields are WHOLE-SAMPLE-scale densities exactly as R’shat$left/right/diffreport them (joint-basis estimates). Per-side CONDITIONAL densities are these times(N-1)/(n_left-1)and(N-1)/(n_right-1)- the exact conversion under the rank-based EDF(0:(N-1))/(N-1)that R (and this port) implement; the CJM 2020 paper’sn/n_sidefactors apply to the paper’s(1/n)*sumEDF, which is not what R computes.There are deliberately no
att/se/conf_intfields: Rrddensityreports no confidence interval anywhere in its output (intervals belong tordplotdensity), and the estimand vocabulary of the treatment-effect estimators does not apply to a density test.- t_stat: float
- p_value: float
- f_left: float
- f_right: float
- f_diff: float
- se_left: float
- se_right: float
- se_diff: float
- n: int
- n_left: int
- n_right: int
- n_eff_left: int
- n_eff_right: int
- h_left: float
- h_right: float
- cutoff: float
- p: int
- q: int
- fitselect: str
- kernel: str
- vcov_type: str
- bwselect: str
- bandwidth_method: str
- masspoints: str
- masspoints_adjusted: bool
- regularize: bool
- n_local_min: int
- n_unique_min: int
- report_all: bool
- to_dataframe()[source]
Tidy per-side table: rows
left/right/diff(plus the*_conventionalrows whenreport_all=True); columnsestimate/se/t_stat/p_value(inference populated on thediffrows only).- Return type:
- __init__(t_stat, p_value, f_left, f_right, f_diff, se_left, se_right, se_diff, f_left_conventional, f_right_conventional, f_diff_conventional, se_left_conventional, se_right_conventional, se_diff_conventional, t_stat_conventional, p_value_conventional, n, n_left, n_right, n_eff_left, n_eff_right, h_left, h_right, bandwidths, cutoff, p, q, fitselect, kernel, vcov_type, bwselect, bandwidth_method, masspoints, masspoints_adjusted, regularize, n_local_min, n_unique_min, report_all)
- Parameters:
t_stat (float)
p_value (float)
f_left (float)
f_right (float)
f_diff (float)
se_left (float)
se_right (float)
se_diff (float)
f_left_conventional (float | None)
f_right_conventional (float | None)
f_diff_conventional (float | None)
se_left_conventional (float | None)
se_right_conventional (float | None)
se_diff_conventional (float | None)
t_stat_conventional (float | None)
p_value_conventional (float | None)
n (int)
n_left (int)
n_right (int)
n_eff_left (int)
n_eff_right (int)
h_left (float)
h_right (float)
bandwidths (DataFrame | None)
cutoff (float)
p (int)
q (int)
fitselect (str)
kernel (str)
vcov_type (str)
bwselect (str)
bandwidth_method (str)
masspoints (str)
masspoints_adjusted (bool)
regularize (bool)
n_local_min (int)
n_unique_min (int)
report_all (bool)
- Return type:
None