Skip to content

v0.18.0

Latest

Choose a tag to compare

@jeongyoonlee jeongyoonlee released this 12 Oct 15:51
d59bb52

This release deprecates positional treatment and y ahead of the v1.0 switch to fit(X, y, treatment, ...), makes causal trees and forests estimate leaves honestly by default, and moves the uplift trees and forests onto the shared Cython tree kernel, with native missing-value support. It also adds benchmark dataset loaders with ground-truth metrics, new CATE evaluation scores, model save() / load(), and a rebuilt documentation site with an end-to-end tutorial, an estimator guide and an FAQ.

The full notes, with measurements, are in the changelog.

⚠️ Breaking Changes

  • BaseSLearner.estimate_ate always returns (ate, lb, ub) and no longer accepts return_ci. ate = learner.estimate_ate(...) now binds a tuple, and passing return_ci raises TypeError; unpack instead: ate, lb, ub = learner.estimate_ate(...). By @su-jin1425 in #1031

Deprecations

  • Positional treatment and y emit a FutureWarning (#854). In v1.0 the positional order becomes fit(X, y, treatment, ...), the scikit-learn convention. Pass both by keyword — learner.fit(X=X, treatment=treatment, y=y) — which is correct before and after the flip. A call left positional will silently train on swapped arguments at v1.0 rather than raise. See the migration guide. By @jeongyoonlee in #975

Behavior Changes

These change fitted models or estimates without any edit to your code.

  • CausalTreeRegressor and CausalRandomForestRegressor default to honesty=True: the tree structure is grown on one half of the sample and leaf values are re-estimated on the other (Athey & Imbens 2016). Pass honesty=False for the previous behavior. By @jeongyoonlee in #1001
  • The default propensity models produce different scores: log-loss model selection over a wider C grid for the logistic models, max_depth=3 (was 8) for GradientBoostedPropensityModel, and a seeded, stratified early-stopping split. By @arpitjain099 in #944, @su-jin1425 in #1033 and @ismailvc1111 in #1051
  • UpliftTreeClassifier and UpliftRandomForestClassifier run on the shared tree kernel. Names, arguments and output shapes are kept; X_val / treatment_val / y_val and early_stopping_eval_diff_scale are ignored with a UserWarning (use prune_fraction instead), and DecisionTree is deprecated. By @jeongyoonlee in #973
  • UpliftRandomForestClassifier defaults to n_jobs=None (was -1), because peak memory grew with the core count. Predictions are unchanged; pass n_jobs=-1 to restore. By @jeongyoonlee in #992
  • causalml.features.load_data returns numpy.ndarray instead of numpy.matrix, and no longer raises when the one-hot encoder produces no columns. By @arpitjain099 in #978 and #979

New Features

  • Benchmark dataset loaders (fetch_lalonde, fetch_ihdp, fetch_twins), ground-truth metrics (pehe, ate_error, policy_risk) and a reproducible leaderboard notebook by @jeongyoonlee in #1015
  • ccp_alpha="cv" on the causal tree: cost-complexity pruning with the tree size chosen by cross-validation by @jeongyoonlee in #1001
  • pretrain=True on CausalTreeRegressor.estimate_ate and BaseDRIVLearner.estimate_ate by @jeongyoonlee in #1024
  • prune_fraction on UpliftTreeClassifier for validation-based pruning inside fit by @jeongyoonlee in #1004
  • Add a model serialization API (save() / load() / load_learner) with version metadata to the meta-learners by @genrichez in #929
  • Extend serialization to the causal and uplift trees and forests, BaseDRIVLearner and IVRegressor by @genrichez in #932
  • Add dr_score() and plug_in_t_score() for evaluating CATE models without ground truth by @aman-coder03 in #930
  • Add rlearner_score() (R-loss) for CATE model evaluation, matching EconML's RScorer by @aman-coder03 in #935
  • Add bootstrap confidence intervals to auuc_score() and qini_score() by @ipezygj in #994
  • Add SensitivityMSM: ATE bounds under the Marginal Sensitivity Model by @aman-coder03 in #925
  • Improve SensitivityMSM robustness and test coverage by @aman-coder03 in #933
  • Add return_components to the R-learner's predict() and fit_predict() by @aman-coder03 in #923
  • Add XGBTClassifier and XGBRClassifier meta-learners by @jeongyoonlee in #942
  • Add a JAX/flax.nnx backend for CEVAE by @xrhd in #957
  • Expose matched index pairs as NearestNeighborMatch.matched_indexes_ by @jeongyoonlee in #940
  • Add node p-value display to plot_causal_tree by @genrichez in #976

Tree Unification (#945)

  • Kernel-backed uplift criteria (KL/ED/Chi) with legacy parity by @jeongyoonlee in #959
  • Add uplift split regularization (n_reg / min_samples_treatment) to the kernel tree by @jeongyoonlee in #961
  • Kernel uplift normalization + CTS and two-class criteria DDP/IT/CIT by @jeongyoonlee in #965
  • Add the honest approach and the IDDP criterion to the kernel uplift tree by @jeongyoonlee in #964
  • Add validation-based pruning to the kernel uplift tree by @jeongyoonlee in #966
  • Add a kernel-backed uplift random forest by @jeongyoonlee in #967
  • Make plot.py work with the kernel uplift tree by @jeongyoonlee in #968
  • Serialization and BaseEstimator conformance for the kernel uplift classes by @jeongyoonlee in #971
  • Add a kernel-vs-legacy uplift tree parity notebook by @jeongyoonlee in #969
  • Native missing-value (NaN) support for the kernel uplift and causal trees by @jeongyoonlee in #972
  • Share the tree kernel: hoist the group criterion base and unify the builders by @jeongyoonlee in #974
  • Switch the public uplift trees to the kernel backend and delete uplift.pyx by @jeongyoonlee in #973

Bug Fixes

  • Fix CausalTreeRegressor.estimate_ate understating the standard error: nominal 95% intervals covered the true ATE 0% of the time, now 88% by @jeongyoonlee in #1007
  • Fix UpliftTreeClassifier.prune leaving per-node group counts stale by @jeongyoonlee in #1004
  • Fix uplift_tree_plot failing with pyparsing 3.3.3 by replacing pydotplus with pydot by @jeongyoonlee in #1053
  • Report an actionable error on macOS without the OpenMP runtime by @su-jin1425 in #1005
  • Fix LRSRegressor.estimate_ate raising KeyError on DataFrame input by @su-jin1425 in #1031
  • Thread random_state to BaseRLearner bootstraps by @su-jin1425 in #1032
  • Accept RandomState instances as the meta-learners' random_state by @su-jin1425 in #1040
  • Supply a default LightGBM outcome learner in the public cate_scoring functions by @su-jin1425 in #1030
  • Give rlearner_score a default outcome learner, and raise ValueError instead of asserting in the CATE scoring functions by @su-jin1425 in #1039
  • Seed and stratify GradientBoostedPropensityModel's early-stopping validation split by @ismailvc1111 in #1051
  • Use a custom propensity_learner on the R-learner's first fit by @arpitjain099 in #937
  • Raise in mape when every target is near zero, and define smape's 0/0 term as 0 by @arpitjain099 in #1050
  • Stop logloss from clipping the caller's prediction array in place by @arpitjain099 in #943
  • Fix NaN scores in FilterSelect's KL filter when a bin has no treated converters by @arpitjain099 in #999
  • Take the exact KL limits at pk = 0 and pk = 1 instead of clamping by @arpitjain099 in #1000
  • Validate a binary outcome in filter_LR / filter_D by @jeongyoonlee in #941
  • Fix the ATE column filled with NaN in SensitivitySelectionBias.summary by @arpitjain099 in #958
  • Fix a NearestNeighborMatch crash when the control pool runs below ratio by @arpitjain099 in #934
  • Preserve the decorated function's name, docstring and signature in timeit by @jeongyoonlee in #987

Documentation

Build / CI

New Contributors

Full Changelog: v0.17.0...v0.18.0