Skip to content

Commit b29b4aa

Browse files
Merge pull request #20 from IBM/version-0.7.1
version 0.7.1 Co-authored-by: Yishai Shimoni <[email protected]>
2 parents 8f04d1a + 5ea3864 commit b29b4aa

File tree

5 files changed

+119
-28
lines changed

5 files changed

+119
-28
lines changed

README.md

+28-15
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@ Causal inference analysis enables estimating the causal effect of
1313
an intervention on some outcome from real-world non-experimental observational data.
1414

1515
This package provides a suite of causal methods,
16-
under a unified scikit-learn-inspired API.
16+
under a unified scikit-learn-inspired API.
1717
It implements meta-algorithms that allow plugging in arbitrarily complex machine learning models.
18-
This modular approach supports highly-flexible causal modelling.
19-
The fit-and-predict-like API makes it possible to train on one set of examples
18+
This modular approach supports highly-flexible causal modelling.
19+
The fit-and-predict-like
20+
API makes it possible to train on one set of examples
2021
and estimate an effect on the other (out-of-bag),
2122
which allows for a more "honest"<sup>1</sup> effect estimation.
2223

2324
The package also includes an evaluation suite.
2425
Since most causal-models utilize machine learning models internally,
2526
we can diagnose poor-performing models by re-interpreting known ML evaluations from a causal perspective.
26-
If you use it in scientific context, please consider citing [Shimoni et al., 2019](https://arxiv.org/abs/1906.00442):
27+
28+
If you use the package, please consider citing [Shimoni et al., 2019](https://arxiv.org/abs/1906.00442):
29+
<details>
30+
<summary>Reference</summary>
31+
2732
```bibtex
2833
@article{causalevaluations,
2934
title={An Evaluation Toolkit to Guide Model Selection and Cohort Definition in Causal Inference},
@@ -34,20 +39,28 @@ If you use it in scientific context, please consider citing [Shimoni et al., 201
3439
```
3540

3641
-------------
42+
</details>
43+
3744
<sup>1</sup> Borrowing [Wager & Athey](https://arxiv.org/abs/1510.04342) terminology of avoiding overfit.
3845

3946

4047
## Installation
4148
```bash
42-
pip install causallib
49+
pip install git+ssh://[email protected]/CausalDev/CausalInference.git
50+
```
51+
To install a specific branch use:
52+
```bash
53+
pip install git+ssh://[email protected]/CausalDev/CausalInference.git@{branch-name}#egg=causallib
4354
```
4455

56+
If installing for development purposes then installation should be performed
57+
with the `-e` flag.
58+
4559
## Usage
46-
In general, the package is imported using the name `causallib`.
47-
Every causal model requires an internal machine-learning model.
60+
The package is imported using the name `causallib`.
61+
Each causal model requires an internal machine-learning model.
4862
`causallib` supports any model that has a sklearn-like fit-predict API
49-
(note some models might require a `predict_proba` implementation).
50-
63+
(note some models might require a `predict_proba` implementation).
5164
For example:
5265
```Python
5366
from sklearn.linear_model import LogisticRegression
@@ -63,7 +76,7 @@ effect = ipw.estimate_effect(potential_outcomes[1], potential_outcomes[0])
6376
Comprehensive Jupyter Notebooks examples can be found in the [examples directory](examples).
6477

6578
### Community support
66-
We use the Slack workspace at [causallib.slack.com](https://causallib.slack.com/) for informal communication.
79+
We use the Slack workspace at [causallib.slack.com](https://causallib.slack.com/) for informal communication.
6780
We encourage you to ask questions regarding causal-inference modelling or
6881
usage of causallib that don't necessarily merit opening an issue on Github.
6982

@@ -74,25 +87,25 @@ Some key points on how we address causal-inference estimation
7487

7588
##### 1. Emphasis on potential outcome prediction
7689
Causal effect may be the desired outcome.
77-
However, every effect is defined by two potential (counterfactual) outcomes.
90+
However, every effect is defined by two potential (counterfactual) outcomes.
7891
We adopt this two-step approach by separating the effect-estimating step
79-
from the potential-outcome-prediction step.
92+
from the potential-outcome-prediction step.
8093
A beneficial consequence to this approach is that it better supports
8194
multi-treatment problems where "effect" is not well-defined.
8295

8396
##### 2. Stratified average treatment effect
8497
The causal inference literature devotes special attention to the population
8598
on which the effect is estimated on.
8699
For example, ATE (average treatment effect on the entire sample),
87-
ATT (average treatment effect on the treated), etc.
100+
ATT (average treatment effect on the treated), etc.
88101
By allowing out-of-bag estimation, we leave this specification to the user.
89102
For example, ATE is achieved by `model.estimate_population_outcome(X, a)`
90103
and ATT is done by stratifying on the treated: `model.estimate_population_outcome(X.loc[a==1], a.loc[a==1])`
91104

92105
##### 3. Families of causal inference models
93106
We distinguish between two types of models:
94107
* *Weight models*: weight the data to balance between the treatment and control groups,
95-
and then estimates the potential outcome by using a weighted average of the observed outcome.
108+
and then estimates the potential outcome by using a weighted average of the observed outcome.
96109
Inverse Probability of Treatment Weighting (IPW or IPTW) is the most known example of such models.
97110
* *Direct outcome models*: uses the covariates (features) and treatment assignment to build a
98111
model that predicts the outcome directly. The model can then be used to predict the outcome
@@ -111,7 +124,7 @@ proper selection on both dimensions of the data to avoid introducing bias:
111124

112125
This is a place where domain expert knowledge is required and cannot be fully and truly automated
113126
by algorithms.
114-
This package assumes that the data provided to the model fit the criteria.
127+
This package assumes that the data provided to the model fit the criteria.
115128
However, filtering can be applied in real-time using a scikit-learn pipeline estimator
116129
that chains preprocessing steps (that can filter rows and select columns) with a causal model at the end.
117130

causallib/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.7.0"
1+
__version__ = "0.7.1"

causallib/evaluation/evaluator.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def score_binary_prediction(self, y_true, y_pred_proba=None, y_pred=None, sample
122122
warnings.warn(str(v))
123123
scores[metric_name] = np.nan
124124

125-
dtype = np.float if all([np.isscalar(score) for score in scores.values()]) else np.dtype(object)
125+
dtype = float if all([np.isscalar(score) for score in scores.values()]) else np.dtype(object)
126126
return pd.Series(scores, dtype=dtype)
127127

128128
def score_regression_prediction(self, y_true, y_pred, sample_weight=None, metrics_to_evaluate=None):

causallib/evaluation/plots.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -478,25 +478,26 @@ def plot_propensity_score_distribution(propensity, treatment, reflect=True, kde=
478478
ax = ax or plt.gca()
479479
if kde and not norm_hist:
480480
warnings.warn("kde=True and norm_hist=False is not supported. Forcing norm_hist from False to True.")
481-
norm_hist=True
481+
norm_hist = True
482482
bins = np.histogram(propensity, bins="auto")[1]
483-
plot_params = dict(bins=bins, density=norm_hist, alpha=0.5, cumulative=cumulative)
483+
plot_params = dict(bins=bins, density=norm_hist, alpha=0.5, cumulative=cumulative)
484484

485485
unique_treatments = np.sort(np.unique(treatment))
486-
for treatment_value in unique_treatments:
486+
for treatment_number, treatment_value in enumerate(unique_treatments):
487487
cur_propensity = propensity.loc[treatment == treatment_value]
488-
cur_color = "C{}".format(treatment_value)
489-
ax.hist(cur_propensity, label = "treatment = {}".format(treatment_value), color=cur_color,**plot_params)
488+
cur_color = "C{}".format(treatment_number)
489+
ax.hist(cur_propensity, label="treatment = {}".format(treatment_value),
490+
color=[cur_color], **plot_params)
490491
if kde:
491492
cur_kde = gaussian_kde(cur_propensity)
492-
min_support = max(0,cur_propensity.values.min() - cur_kde.factor)
493-
max_support = min(1, cur_propensity.values.max() + cur_kde.factor)
494-
X_plot = np.linspace(min_support,max_support,200)
493+
min_support = max(0, cur_propensity.values.min() - cur_kde.factor)
494+
max_support = min(1, cur_propensity.values.max() + cur_kde.factor)
495+
X_plot = np.linspace(min_support, max_support, 200)
495496
if cumulative:
496497
density = np.array([cur_kde.integrate_box_1d(X_plot[0], x_i) for x_i in X_plot])
497-
ax.plot(X_plot,density,color=cur_color,)
498-
else:
499-
ax.plot(X_plot,cur_kde.pdf(X_plot),color=cur_color,)
498+
ax.plot(X_plot, density, color=cur_color, )
499+
else:
500+
ax.plot(X_plot, cur_kde.pdf(X_plot), color=cur_color, )
500501
if reflect:
501502
if len(unique_treatments) != 2:
502503
raise ValueError("Reflecting density across X axis can only be done for two groups. "

causallib/tests/test_plots.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# (C) Copyright 2020 IBM Corp.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Created on Nov 12, 2020
16+
17+
from sklearn.linear_model import LogisticRegression, LinearRegression
18+
from causallib.evaluation import PropensityEvaluator, OutcomeEvaluator
19+
from causallib.estimation import DoublyRobustVanilla, IPW, StratifiedStandardization
20+
from causallib.datasets import load_nhefs
21+
import unittest
22+
import pandas as pd
23+
from sklearn.utils import Bunch
24+
import matplotlib
25+
matplotlib.use('Agg')
26+
27+
28+
class TestPlots(unittest.TestCase):
29+
@classmethod
30+
def setUpClass(self):
31+
self.data = load_nhefs()
32+
ipw = IPW(LogisticRegression(solver="liblinear"), truncate_eps=0.05)
33+
std = StratifiedStandardization(LinearRegression())
34+
self.dr = DoublyRobustVanilla(std, ipw)
35+
self.dr.fit(self.data.X, self.data.a, self.data.y)
36+
self.prp_evaluator = PropensityEvaluator(self.dr.weight_model)
37+
self.out_evaluator = OutcomeEvaluator(self.dr.outcome_model)
38+
39+
def propensity_plot_by_name(self, test_names, alternate_a=None):
40+
a = self.data.a if alternate_a is None else alternate_a
41+
nhefs_plots = self.prp_evaluator.evaluate_simple(
42+
self.data.X, a, self.data.y, plots=test_names)
43+
[self.assertIsNotNone(x) for x in nhefs_plots.plots.values()]
44+
return True
45+
46+
def outcome_plot_by_name(self, test_names):
47+
nhefs_plots = self.out_evaluator.evaluate_simple(
48+
self.data.X, self.data.a, self.data.y, plots=test_names)
49+
[self.assertIsNotNone(x) for x in nhefs_plots.plots.values()]
50+
return True
51+
52+
def propensity_plot_multiple_a(self, test_names):
53+
self.assertTrue(self.propensity_plot_by_name(test_names, alternate_a=self.data.a.astype(int)))
54+
self.assertTrue(self.propensity_plot_by_name(test_names, alternate_a=self.data.a.astype(float)))
55+
# self.assertTrue(self.propensity_plot_by_name(test_names, alternate_a=self.data.a.astype(str).factorize()))
56+
57+
58+
def test_weight_distribution_plot(self):
59+
self.propensity_plot_multiple_a(["weight_distribution"])
60+
61+
def test_propensity_roc_plots(self):
62+
self.propensity_plot_multiple_a(['roc_curve'])
63+
64+
def test_precision_plots(self):
65+
self.propensity_plot_multiple_a(['precision'])
66+
67+
def test_precision_plots(self):
68+
self.propensity_plot_multiple_a(['covariate_balance_love'])
69+
70+
def test_propensity_multiple_plots(self):
71+
self.propensity_plot_multiple_a(['roc_curve', 'covariate_balance_love'])
72+
73+
def test_accuracy_plot(self):
74+
self.assertTrue(self.outcome_plot_by_name(
75+
["common_support", "continuous_accuracy"]))
76+
77+
# todo: add more tests (including ones that raise exceptions). No point in doing this right now since a major refactoring for the plots is ongoing

0 commit comments

Comments
 (0)