Changelog¶
All notable changes to this project are documented here. The format follows Keep a Changelog and the project uses Semantic Versioning.
Unreleased¶
0.16.0 - 2026-10-26¶
Added¶
- Simulated data sets: the data generating processes of DoubleML's 17
make_*functions. Rust:dml_core::datasets(PlrCcddhnr2018,PlrTurrell2018,ConfoundedPlr,PlivChs2015,PlivMultiwayClusterCkms2021,LplrLzz2020,PlprCp2025,IrmData,ConfoundedIrm,IrmDiscreteTreatments,IivmData,SsmData,HeterogeneousData,DidSz2020,DidCs2021,SimpleRdd): structs with DoubleML's parameters and defaults andgenerate(seed), with conversions toDmlData,StaticPanel,PanelDataandRepeatedCrossSections. Python:dml_rs.datasetswith DoubleML's function names and signatures (return_type="array"or"DataFrame",random_state). Draws come from a seeded ChaCha generator; the tests compare the distributions with DoubleML's generators.
0.15.0 - 2026-10-26¶
Added¶
- The RD back end now ports
rdrobust2.1.1's options:dml_core::rd::rd_estimateandrd_bandwidthswithRdOptions(polynomial ordersp/q,Kernel, all tenBwSelectbandwidth selectors,Vcenearest-neighbour or HC0–HC3,nnmatch,level,MassPoints,bwcheck,bwrestrict,stdvars,scaleregul,sharpbw,rho,scalepar,covs_drop) andRdDatawith covariates and cluster labels (cluster-robustcr1).rdrobustandrdbwselectremain as shortcuts with the defaults. A fixture with 42 option sets matchesrdrobustto1e-9. - RDFlex passes these options to the RD stage, as DoubleML's
RDFlex(**kwargs):RdFlex::rd_options,rd_covariates,rd_clusters, and a classifier for a binary outcome (RdFlex::sharp_binary,fuzzy_binary,from_boxed_binary). Python:RDFlex(..., p=2, kernel="epa", covs=Z, cluster=g, ...), and a classifierml_gfor a binary outcome. Four parity fixtures; the Python tests match DoubleML to1e-9.
0.14.0 - 2026-10-26¶
Added¶
- Framework arithmetic, as DoubleML's
DoubleMLFramework:&a + &b,&a - &b,c * &fitand-&fitonDmlFit(the operator traitsAdd,Sub,Mul<f64>andNeg), and the publicDmlFit::linear_combination(weights, names), all with combined scores so inference accounts for the correlation between the estimates; sensitivity bounds add up with absolute weights.DmlFit::concatno longer requires equal fold counts. Python:Framework(model.framework,+,-,*,linear_combination),dml_rs.concat, andCausalContrastandPotentialQuantilesare frameworks. A parity fixture and Python tests match DoubleML to1e-9. - Sensitivity analysis for staggered DiD (panels and repeated cross-sections), as DoubleML's
DoubleMLDIDMulti: every ATT(g, t), the aggregations (Pythonaggregate(...).aggregated_frameworks/overall_aggregated_framework) and benchmarks (StaggeredDid::sensitivity_benchmark,PanelData::without_covariates,RepeatedCrossSections::without_covariates; Pythonsensitivity_benchmark). The six staggered fixtures now check the bounds, robustness values and benchmarks. - External predictions for PQ, LPQ and LPLR. LPLR fits report the inner predictions of
ml_Mandml_a(ml_M_inner_k,ml_a_inner_k), which externalml_M/ml_aneed too, as DoubleML.
0.13.0 - 2026-10-25¶
Added¶
- PLIV with several instruments (
DmlData::with_instruments; residualised instruments combined by two-stage least squares), the IV-type score for one instrument (PlivScore,ml_g), and DoubleML's partial Z and partial XZ variants (PlivVariant,Pliv::partial_z,Pliv::partial_xz; PythonPLIV(partial=...)). Five parity fixtures; the Python tests match DoubleML to1e-9. -
Propensity calibration, as DoubleML's
ps_processor_config:Calibration::Isotonic/IsotonicCrossFitted(Pythoncalibration="isotonic"/"isotonic_cv") calibrates the cross-fitted propensity scores with an isotonic regression before clipping, for IRM, APO, APOS, IIVM, SSM, PQ, LPQ, CVaR, QTE, DID, DIDCS and staggered DID. Newpropensitymodule withIsotonicRegression, a port of scikit-learn's. Six parity fixtures; the Python tests match DoubleML to1e-9. -
External nuisance predictions, as DoubleML's
external_predictions(DmlData::with_external_predictions(ExternalPredictions),StaticPanel::with_external_predictions; Pythonfit(..., external_predictions=...)) for PLR, IRM, APO, PLIV, IIVM, DID, DIDCS and PLPR: supplied learners are not fitted and report no loss. Models read them throughScoreInput::predictions_or;ScoreInputgainstreatmentandexternal. -
Tuning by grid search:
GridSearchRegressor(cross-validated mean squared error) andGridSearchClassifier(log loss, stratified folds) choose the best of several candidate learners and refit it; as nuisance learners they tune inside every cross-fitting fold, as DoubleML'stune(..., tune_on_folds=True). Python classes of the same names accept built-in and scikit-learn candidates and reproduceGridSearchCV(scoring="neg_mean_squared_error"). -
sensitivity_plotandsensitivity_contours(Python), as DoubleML'ssensitivity_plot: contours of the effect's bound (or confidence bound) overcf_dandcf_ywith the scenario and benchmarks marked (matplotlib), and the grid as data; the grid comes from the new nativeDmlResult.sensitivity_gridand matches DoubleML's to1e-9.
Fixed¶
- Sensitivity analysis: when
ν²is not positive in some repetition of an estimate, every repetition now falls back to the non-orthogonalmean(α²), as DoubleML (previously only the affected repetition did).
Changed¶
- Nuisance names in
ScoreEstimate::predictionsandNuisanceTarget::nameareName(Cow<'static, str>), so models can name learners at run time (e.g.ml_m_z2). The constructors accept string literals as before. DmlData::zreturns the instrument only when exactly one is attached;instrumentsreturns all. IIVM, LPQ and SSM reject several instruments with a clear error.
0.12.0 - 2026-10-25¶
Added¶
- Sample selection model (
Ssm/SSM), as DoubleML'sDoubleMLSSM: the average treatment effect when the outcome is observed only for a selected subsample, with selection missing at random or nonignorable (with an instrument for selection; nested cross-fitting within each fold).DmlData::with_selection_indicator. Four parity fixtures; the Python tests match DoubleML to1e-9with scikit-learn learners. - Logistic partially linear regression (
Lplr/LPLR), as DoubleML'sDoubleMLLPLR: the log odds ratio of a treatment on a binary outcome, with the nuisance-space and instrument scores, double cross-fitting (n_folds_inner;fit_with_inner_foldsreplays given inner splits) and Newton's method on the non-linear score.ml_mmay be a regressor or, for a binary treatment, a classifier (TreatmentLearner). Four parity fixtures; the Python tests match DoubleML to1e-9. - Partially linear panel regression (
Plpr/PLPR), as DoubleML'sDoubleMLPLPR: static panels (StaticPanel, long format with unit and period) transformed by first differences, within-group demeaning or correlated random effects (PanelApproach), then a partially linear regression with unit-clustered folds and standard errors. Five parity fixtures (including unbalanced panels); the Python tests match DoubleML to1e-9. Python accepts any sortable unit and period labels (e.g. strings and dates). - Regression discontinuity with flexible covariate adjustment (
RdFlex/RDFlex), as DoubleML'sRDFlex: sharp and fuzzy designs, cutoff, first-stage specifications and kernels (FirstStage,RdKernel), iterative bandwidth updates. Its back end, the newrdmodule, ports the defaults of therdrobustpackage (rdbwselectMSE-optimal bandwidths with the mass-point floor;rdrobustlocal linear estimates with bias correction, nearest-neighbour standard errors and conventional / bias-corrected / robust intervals) and matches it to1e-9. Seven parity fixtures; the Python tests match DoubleML to1e-9.parity/requirements.txtpinsrdrobust. Classifier::fit_weighted(default: unsupported) and sample weights forLogisticRegression; Python classifiers receivesample_weight.
0.11.0 - 2026-10-25¶
Added¶
- Quantile models, as DoubleML: potential quantiles (
Pq/PQ), local potential quantiles of the compliers with a binary instrument (Lpq/LPQ), conditional value at risk (Cvar/CVAR), and quantile treatment effects at several quantiles with joint inference (Qte/QTE, scores PQ, LPQ and CVaR;QTE.potentialgives the potential quantiles). Includes DoubleML's nested preliminary estimate, the start value carried across repetitions, and the kernel density standard errors. Default sample splits are stratified by treatment (and instrument). Seven parity fixtures; the Python tests match DoubleML to1e-9with scikit-learn learners.
Changed¶
- IRM, APO and IIVM with
normalize_ipw=Truecompute the normalising means with NumPy's pairwise summation, so they agree with DoubleML to the last bit (previously within about1e-15).
0.10.0 - 2026-10-25¶
Added¶
- CATE / GATE for PLR, as DoubleML's
DoubleMLPLR.cate/gate(both scores: the outcome residual on the basis scaled by each repetition's treatment residual), and conditional average potential outcomes for APO (capo/gapo). Models declare their signal through the newCateSignal(ScoreEstimate::with_cate_signal);Blp::fit_basestakes one basis per repetition. weightsfor IRM and APO, as DoubleML: weighted average effects (e.g. the effect in a subgroup), 0/1 subgroup weights with the ATTE score, and separate per-repetitionweights_bar. Python:IRM(weights=...)/APO(weights=...)with an array or DoubleML's dict form.- Policy trees, as DoubleML's
DoubleMLPolicyTree:DmlFit::policy_tree/IRM.policy_treefit a shallow decision tree to the sign of the doubly robust effect signal, weighted by its size, with scikit-learn's growth rules and minimal cost-complexity pruning (same pruning order), and give treatment recommendations and the rules as text. Python:dml_rs.PolicyTree. ScoreInput::rep, the index of the repetition being estimated.- Multi-valued treatments, as DoubleML's
DoubleMLAPOandDoubleMLAPOS:Apoestimates the average potential outcomeE[Y(d)]of one treatment level with the doubly robust score;Aposfits several levels on shared sample splits, andAposFit::causal_contrastgives the contrasts between levels with joint inference and DoubleML's combined sensitivity elements. Default splits are stratified by the treatment. Python:dml_rs.APO,dml_rs.APOSandAPOS.causal_contrast, which returns aCausalContrastresult (summary, confidence intervals, bootstrap, sensitivity analysis). DmlFit::concatstacks fits of the same data and sample splits for joint inference (DoubleML'sconcat).- DoubleML parity fixtures for APO (repeated splits, normalised IPW, sensitivity benchmark) and APOS (levels, contrasts against one and two reference levels, sensitivity), and Monte-Carlo tests of contrast coverage, including clustered data.
- Documentation site at https://asadgudzevic94.github.io/dml-rs/: getting started, a user guide for models, learners and inference, the Python API reference, validation details (including the known, deliberate differences from DoubleML and scikit-learn), and five worked notebooks (partially linear model, binary treatments and heterogeneity, difference-in-differences, sensitivity analysis, clustered data and learner diagnostics), each checked against the true effects or DoubleML.
- Python type information: stubs for the native extension (
_dml_rs.pyi), annotations on the estimator classes and apy.typedmarker, so editors autocompletedml_rsand type checkers (mypy, pyright) check calls, e.g. invalidscoreorscoringvalues. CI checks the stubs against the compiled extension withmypy.stubtestand type-checks the package.
Changed¶
- Linear combinations of clustered fits (APOS contrasts) keep the cluster structure and use the cluster-robust variance of the combined score; DoubleML's framework arithmetic ignores the correlation within clusters there.
LinearScoreModelhas an optionalstratamethod: models can stratify their default sample splits (used byApo).
0.9.0 - 2026-10-24¶
Added¶
LassoCVandLogisticRegressionCV, the cross-validated learners most DoubleML analyses use for their nuisances, with scikit-learn's grids, folds and selection rules:LassoCVfits a warm-started path of 100 penalties fromα_maxdown to1e-3 · α_maxon each of 5 contiguous folds and keeps the one with the lowest mean test MSE (sample weights supported);LogisticRegressionCVtries 10 values ofCfrom1e-4to1e4on scikit-learn's stratified folds. Both refit on all the data.CvScoring::LogLoss(default; scikit-learn's default from version 1.11) orCvScoring::Accuracy. With scikit-learn's default settings andn > p,LassoCVreproduces scikit-learn's chosen penalty and coefficients to ~1e-15 and is 2–40× faster; PLR withLassoCVnuisances is ~10× faster than DoubleML. Python:dml_rs.LassoCVanddml_rs.LogisticRegressionCVwith scikit-learn's parameter and attribute names (alpha_,alphas_,mse_path_,C_,Cs_,scores_,coef_,intercept_).- DoubleML parity fixtures with cross-validated nuisance learners (
plr_lasso_cv,irm_ate_lasso_cv_logit_cv) andparity/bench_cv.py.
Changed¶
Lassonow follows scikit-learn's coordinate descent exactly: the stopping rule is scikit-learn's (after a sweep in which no coefficient moves by more thantol · max|w|, the duality gap must be at mosttol · ‖y − ȳ‖² / n), the defaulttolis scikit-learn's1e-4(was a step-size rule with1e-12), and gap safe screening skips coefficients that are provably zero. Withn > pthe solver works on the Gram matrix. Results at default settings match scikit-learn'sLasso()to ~1e-15 whenn > p; withp > n, where the loose defaulttolcan stop at slightly different points depending on floating-point rounding, both agree to within the tolerance. Pass a smallertolfor a more precise optimum.
0.8.0 - 2026-10-24¶
Added¶
- Cluster-robust DML for one-way and two-way clustered data (Chiang, Kato, Ma & Sasaki, 2022), as
DoubleML's
cluster_vars:DmlData::with_clusters/with_two_way_clustersmake every model assign whole clusters to folds (two-way: the product of the per-variable folds), solve the fold-weighted score and report cluster-robust standard errors; sensitivity bounds use the clustered variance.fit_with_cluster_foldsreplays given cluster splits,DmlFit::n_clustersreports the number of clusters. The multiplier bootstrap returns an error for clustered fits, as in DoubleML. - Learner diagnostics:
DmlFit::nuisance_loss/nuisance_lossesgive the out-of-sample RMSE (regressions) or log loss (classifications) of every nuisance learner on its own target, as DoubleML'snuisance_loss;DmlFit::nuisance_targetsreturns the targets (NaNwhere a learner does not apply) for custom metrics. Shown in the fit summary. LossKindandNuisanceTarget, with which customLinearScoreModels declare their nuisance targets (ScoreEstimate::with_targets).- Python:
cluster=(one or two columns of any label type) in thefitof PLR, IRM, PLIV, IIVM, DID and DIDCS,n_clusters_,nuisance_loss_(DoubleML's layout) andevaluate_learners(learners=None, metric=None). - DoubleML parity fixtures for one-way (PLR with repeated splits, IRM) and two-way (PLR, PLIV) clustering, nuisance losses in every fixture, and a Monte-Carlo test in which iid standard errors under-cover with clustered errors while cluster-robust ones do not.
Changed¶
ScoreEstimatehas a newtargetsfield (empty by default, so existing custom models keep working; without targets no losses are reported for them).
0.7.0 - 2026-10-24¶
Added¶
- Sensitivity analysis for omitted confounders (Chernozhukov et al., 2022), as DoubleML's
sensitivity_analysis/sensitivity_benchmark, for PLR (both scores), IRM (ATE / ATTE) and DiD for panels and repeated cross-sections:DmlFit::sensitivity_analysisgives bounds on θ, one-sided confidence bounds and robustness values (RV, RVa);DmlFit::sensitivity_benchmarkand the models'sensitivity_benchmarkcalibratecf_y,cf_dandrhoagainst observed covariates;DmlFit::sensitivity_elementsexposes σ² and ν². Robustness values are solved exactly (DoubleML uses a bounded numerical minimiser). DmlData::without_covariates.- Python:
sensitivity_analysis,sensitivity_summary,sensitivity_params_andsensitivity_benchmarkon PLR, IRM, DID and DIDCS. - DoubleML parity checks for sensitivity analysis in all PLR, IRM and DiD fixtures.
Changed¶
ScoreEstimateis now#[non_exhaustive]with an optionalsensitivityfield; customLinearScoreModelimplementations build it withScoreEstimate::new(score, predictions).
0.6.0 - 2026-10-24¶
Added¶
GradientBoostingRegressor(squared error) andGradientBoostingClassifier(log loss with Newton leaf updates): shallow CART trees on the negative gradient with learning rate, depth and leaf-size limits, stochastic row subsampling, random feature subsets, sample weights and feature importances. Predictions reproduce scikit-learn'sGradientBoostingRegressor/GradientBoostingClassifierto ~1e-14 (the classifier can differ only where two splits have exactly equal gain, which scikit-learn itself resolves differently perrandom_state). About 9× faster than DoubleML with scikit-learn boosting for PLR. Python:dml_rs.GradientBoostingRegressor/dml_rs.GradientBoostingClassifier.CONTRIBUTING.mdandCITATION.cff(GitHub's "Cite this repository").
Changed¶
- README rewritten: model overview, reproducible quick start, benchmarks, validation and citation; matching crates.io and PyPI pages.
0.5.0 - 2026-10-24¶
Added¶
- Staggered DID for repeated cross-sections:
RepeatedCrossSectionsdata, accepted byStaggeredDid::fitalongsidePanelData(both convert into the newStaggeredInput), as DoubleML'sDoubleMLDIDMulti(panel=False); folds are stratified by group × period. - Python:
StaggeredDID(..., panel=False). - DoubleML parity fixtures for staggered DID with repeated cross-sections.
Changed¶
- Staggered aggregation weights use each cohort's share of observations (DoubleML's convention). Identical to unit shares for balanced panels; for unbalanced panels results now match DoubleML.
StaggeredDid::fit,fit_with_foldsandgroup_timestakeimpl Into<StaggeredInput>; existing calls with&PanelDatakeep working.
0.4.0 - 2026-10-23¶
Added¶
StaggeredDid: difference-in-differences with staggered adoption (Callaway & Sant'Anna, 2021; DoubleML'sDoubleMLDIDMultifor panels). Estimates every group-time effect ATT(g, t) with the doubly robust panel DID score; never-treated or not-yet-treated controls, anticipation periods, standard / all / universal base periods. Results are aDmlFit, so joint bootstrap bands and Romano–Wolf adjustment work across all ATT(g, t).StaggeredDidFit::aggregateby cohort, calendar time or event time, plus the overall effect, with influence-function based standard errors.PanelData(wide, withNaNfor missing periods, orfrom_long), unbalanced panels supported.KFold::split_stratified.- Python:
dml_rs.StaggeredDID(fiton long arrays orfit_frameon a DataFrame,aggregate). - DoubleML parity fixtures for three staggered settings, including all aggregations.
0.3.0 - 2026-10-23¶
Added¶
Iivm: interactive IV model for the local average treatment effect (LATE) with a binary instrument; always-/never-taker subgroup switches for one-sided non-compliance.Did(panel data) andDidCs(repeated cross-sections): doubly robust difference-in-differences for the ATT (Sant'Anna & Zhao, 2020) with observational and experimental scores and optional in-sample normalisation.DmlData::with_time_indicatorfor the period indicator.cross_fit_predict_proba_subset.- Python:
dml_rs.IIVM,dml_rs.DID,dml_rs.DIDCS. - DoubleML parity fixtures for IIVM and all DID score / normalisation variants.
Changed¶
ScoreInputgains a time-indicator fieldtand is now#[non_exhaustive]; customLinearScoreModelimplementations that destructure it need a trailing...
Notes¶
Iivm::normalize_ipwnormalises with the instrument, whereas DoubleML 0.11 normalises with the treatment; results differ from DoubleML only when this option is enabled (reported upstream as DoubleML#411).
0.2.0 - 2026-10-23¶
Added¶
RandomForestRegressorandRandomForestClassifier: CART forests in pure Rust with bootstrap, random feature subsets (MaxFeatures), depth and leaf-size limits, sample weights and feature importances. Trees are grown in parallel and are reproducible for a given seed regardless of the number of threads. Also exposed in Python asdml_rs.RandomForestRegressoranddml_rs.RandomForestClassifier(about 3–6× faster than DoubleML with scikit-learn forests for PLR).- CI job checking the minimum supported Rust version (1.80).
0.1.0 - 2026-10-23¶
First release.
Added¶
DmlDatawith one or more treatments and an optional instrument.- Seeded K-fold cross-fitting with repeated sample splitting, fitted in parallel (rayon).
- Models:
Plr(partialling-out and IV-type scores),Irm(ATE / ATTE, propensity clipping, normalised IPW),Pliv; extensible through theLinearScoreModeltrait. - Inference: standard errors, t-statistics, p-values, confidence intervals; multiplier bootstrap (normal, Bayes, wild), joint confidence intervals, Romano–Wolf, Holm and Bonferroni adjustment.
- CATE meta-learners (S, T, X, R, DR) and best-linear-predictor / GATE inference.
- Learners:
Ols(with ridge),Lasso,LogisticRegression; weighted fitting. - Python package
dml-rswith a scikit-learn style API accepting any scikit-learn estimator. - Numerical parity tests against Python DoubleML 0.11.4.