diff --git a/doubleml/rdd/rdd.py b/doubleml/rdd/rdd.py index a08e9f144..e91d6a34d 100644 --- a/doubleml/rdd/rdd.py +++ b/doubleml/rdd/rdd.py @@ -4,7 +4,11 @@ from collections.abc import Callable from scipy.stats import norm -from rdrobust import rdrobust, rdbwselect +try: + from rdrobust import rdrobust, rdbwselect + _rdrobust_available = True +except ImportError: + _rdrobust_available = False from sklearn.base import clone from sklearn.utils.multiclass import type_of_target @@ -105,6 +109,9 @@ def __init__(self, fs_kernel="triangular", **kwargs): + if not _rdrobust_available: + raise ImportError("rdrobust is not installed. Please install it using 'pip install DoubleML[rdd]'") + self._check_data(obj_dml_data, cutoff) self._dml_data = obj_dml_data diff --git a/doubleml/rdd/tests/conftest.py b/doubleml/rdd/tests/conftest.py index 1297572f4..c99973b7b 100644 --- a/doubleml/rdd/tests/conftest.py +++ b/doubleml/rdd/tests/conftest.py @@ -7,7 +7,11 @@ from doubleml import DoubleMLData from doubleml.rdd import RDFlex -from rdrobust import rdrobust +try: + from rdrobust import rdrobust + _rdrobust_available = True +except ImportError: + _rdrobust_available = False from sklearn.dummy import DummyRegressor, DummyClassifier @@ -25,6 +29,9 @@ def predict_dummy(): - make predictions using rdrobust as a reference """ def _predict_dummy(data: DoubleMLData, cutoff, alpha, n_rep, p, fs_specification, ml_g=ml_g_dummy): + if not _rdrobust_available: + raise ImportError("rdrobust is not installed. Please install it using 'pip install DoubleML[rdd]'") + dml_rdflex = RDFlex( data, ml_g=ml_g, diff --git a/doubleml/rdd/tests/test_rdd_classifier.py b/doubleml/rdd/tests/test_rdd_classifier.py index 166ab192c..adbcfcee9 100644 --- a/doubleml/rdd/tests/test_rdd_classifier.py +++ b/doubleml/rdd/tests/test_rdd_classifier.py @@ -24,6 +24,6 @@ dml_rdflex = RDFlex(dml_data, ml_g=LogisticRegression(), ml_m=LogisticRegression(), fuzzy=True) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_classifier(): dml_rdflex.fit() diff --git a/doubleml/rdd/tests/test_rdd_classifier_fuzzy.py b/doubleml/rdd/tests/test_rdd_classifier_fuzzy.py index 18ea2ce12..56be5fc02 100644 --- a/doubleml/rdd/tests/test_rdd_classifier_fuzzy.py +++ b/doubleml/rdd/tests/test_rdd_classifier_fuzzy.py @@ -62,37 +62,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/doubleml/rdd/tests/test_rdd_classifier_fuzzy_left.py b/doubleml/rdd/tests/test_rdd_classifier_fuzzy_left.py index 50235399d..ff65bff2f 100644 --- a/doubleml/rdd/tests/test_rdd_classifier_fuzzy_left.py +++ b/doubleml/rdd/tests/test_rdd_classifier_fuzzy_left.py @@ -62,37 +62,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/doubleml/rdd/tests/test_rdd_classifier_fuzzy_right.py b/doubleml/rdd/tests/test_rdd_classifier_fuzzy_right.py index 21d1b5c39..2969663bc 100644 --- a/doubleml/rdd/tests/test_rdd_classifier_fuzzy_right.py +++ b/doubleml/rdd/tests/test_rdd_classifier_fuzzy_right.py @@ -62,37 +62,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/doubleml/rdd/tests/test_rdd_classifier_sharp.py b/doubleml/rdd/tests/test_rdd_classifier_sharp.py index 2bd8faa40..b0a742a40 100644 --- a/doubleml/rdd/tests/test_rdd_classifier_sharp.py +++ b/doubleml/rdd/tests/test_rdd_classifier_sharp.py @@ -62,37 +62,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/doubleml/rdd/tests/test_rdd_default_values.py b/doubleml/rdd/tests/test_rdd_default_values.py index b6e7eafc5..f103b68cd 100644 --- a/doubleml/rdd/tests/test_rdd_default_values.py +++ b/doubleml/rdd/tests/test_rdd_default_values.py @@ -30,6 +30,6 @@ def _assert_resampling_default_settings(dml_obj): assert dml_obj.fuzzy is False -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_defaults(): _assert_resampling_default_settings(dml_rdflex) diff --git a/doubleml/rdd/tests/test_rdd_exceptions.py b/doubleml/rdd/tests/test_rdd_exceptions.py index 3defef5da..0e524a5b2 100644 --- a/doubleml/rdd/tests/test_rdd_exceptions.py +++ b/doubleml/rdd/tests/test_rdd_exceptions.py @@ -57,7 +57,7 @@ def predict_proba(self, X): ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_data(): # DoubleMLData msg = r"The data must be of DoubleMLData type. \[\] of type was passed." @@ -101,7 +101,7 @@ def test_rdd_exception_data(): _ = RDFlex(tmp_dml_data, ml_g) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_cutoff(): msg = "Cutoff value has to be a float or int. Object of type passed." with pytest.raises(TypeError, match=msg): @@ -112,14 +112,14 @@ def test_rdd_exception_cutoff(): _ = RDFlex(dml_data, ml_g, cutoff=200) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_warning_fuzzy(): msg = 'A sharp RD design is being estimated, but the data indicate that the design is fuzzy.' with pytest.warns(UserWarning, match=msg): _ = RDFlex(dml_data, ml_g, cutoff=0.1) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_warning_treatment_assignment(): msg = ("Treatment probability within bandwidth left from cutoff higher than right from cutoff.\n" "Treatment assignment might be based on the wrong side of the cutoff.") @@ -129,7 +129,7 @@ def test_rdd_warning_treatment_assignment(): _ = RDFlex(tmp_dml_data, ml_g, ml_m, fuzzy=True) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_learner(): # ml_g @@ -163,7 +163,7 @@ def test_rdd_exception_learner(): _ = RDFlex(tmp_dml_data, ml_g, ml_m, fuzzy=False) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_resampling(): # n_folds msg = r"The number of folds must be of int type. \[1\] of type was passed." @@ -182,7 +182,7 @@ def test_rdd_exception_resampling(): _ = RDFlex(dml_data, ml_g, ml_m, n_rep=0) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_kernel(): msg = "fs_kernel must be either a string or a callable. 2 of type was passed." with pytest.raises(TypeError, match=msg): @@ -192,14 +192,14 @@ def test_rdd_exception_kernel(): _ = RDFlex(dml_data, ml_g, ml_m, fs_kernel='rbf') -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_h_fs(): msg = "Initial bandwidth 'h_fs' has to be a float. Object of type passed." with pytest.raises(TypeError, match=msg): _ = RDFlex(dml_data, ml_g, ml_m, h_fs=1) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_fs_specification(): msg = "fs_specification must be a string. 1 of type was passed." with pytest.raises(TypeError, match=msg): @@ -211,7 +211,7 @@ def test_rdd_exception_fs_specification(): _ = RDFlex(dml_data, ml_g, ml_m, fs_specification='local_constant') -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_exception_fit(): rdd_model = RDFlex(dml_data, ml_g, ml_m) msg = (r"The number of iterations for the iterative bandwidth fitting must be of int type. \[0\] of type " diff --git a/doubleml/rdd/tests/test_rdd_fuzzy.py b/doubleml/rdd/tests/test_rdd_fuzzy.py index 6203abcf0..8e41b3c5e 100644 --- a/doubleml/rdd/tests/test_rdd_fuzzy.py +++ b/doubleml/rdd/tests/test_rdd_fuzzy.py @@ -59,37 +59,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/doubleml/rdd/tests/test_rdd_fuzzy_left.py b/doubleml/rdd/tests/test_rdd_fuzzy_left.py index 2c9f887a0..f8ca4a3e2 100644 --- a/doubleml/rdd/tests/test_rdd_fuzzy_left.py +++ b/doubleml/rdd/tests/test_rdd_fuzzy_left.py @@ -59,37 +59,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/doubleml/rdd/tests/test_rdd_fuzzy_right.py b/doubleml/rdd/tests/test_rdd_fuzzy_right.py index f89d9d2a3..627bfde91 100644 --- a/doubleml/rdd/tests/test_rdd_fuzzy_right.py +++ b/doubleml/rdd/tests/test_rdd_fuzzy_right.py @@ -59,37 +59,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/doubleml/rdd/tests/test_rdd_return_types.py b/doubleml/rdd/tests/test_rdd_return_types.py index 418bc0c43..32b6241cd 100644 --- a/doubleml/rdd/tests/test_rdd_return_types.py +++ b/doubleml/rdd/tests/test_rdd_return_types.py @@ -50,7 +50,7 @@ def _assert_return_types_after_fit(dml_obj): # TODO: Add Coefficient tests -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_returntypes(): _assert_return_types(dml_rdflex) _assert_return_types_after_fit(dml_rdflex) diff --git a/doubleml/rdd/tests/test_rdd_sharp.py b/doubleml/rdd/tests/test_rdd_sharp.py index 686276e99..b2b95968d 100644 --- a/doubleml/rdd/tests/test_rdd_sharp.py +++ b/doubleml/rdd/tests/test_rdd_sharp.py @@ -59,37 +59,37 @@ def predict_nonplacebo(predict_dummy, data, cutoff, alpha, p, n_rep, fs_specific ) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_coef(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_coef(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['coef'], reference['coef'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_se(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_se(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['se'], reference['se'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_placebo_ci(predict_placebo): reference, actual = predict_placebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) -@pytest.mark.ci +@pytest.mark.ci_rdd def test_rdd_nonplacebo_ci(predict_nonplacebo): reference, actual = predict_nonplacebo assert np.allclose(actual['ci'], reference['ci'], rtol=1e-9, atol=1e-4) diff --git a/pytest.ini b/pytest.ini index 08130a47a..4d3130e33 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,3 +2,8 @@ [pytest] markers = ci: mark a test as a continuous integration test which will be executed in github actions. + ci_rdd: mark a test as a continuous integration test which will be executed in github actions and is included in the rdd submodule. + +filterwarnings = + ignore:.*A sharp RD design is being estimated, but the data indicate that the design is fuzzy.*:UserWarning + ignore:.*A learner ml_m has been provided for for a sharp design but will be ignored. A learner ml_m is not required for estimation.*:UserWarning \ No newline at end of file