Code Monkey home page Code Monkey logo

py-why / econml Goto Github PK

View Code? Open in Web Editor NEW
3.5K 76.0 676.0 44.12 MB

ALICE (Automated Learning and Intelligence for Causation and Economics) is a Microsoft Research project aimed at applying Artificial Intelligence concepts to economic decision making. One of its goals is to build a toolkit that combines state-of-the-art machine learning techniques with econometrics in order to bring automation to complex causal inference problems. To date, the ALICE Python SDK (econml) implements orthogonal machine learning algorithms such as the double machine learning work of Chernozhukov et al. This toolkit is designed to measure the causal effect of some treatment variable(s) t on an outcome variable y, controlling for a set of features x.

Home Page: https://www.microsoft.com/en-us/research/project/alice/

License: Other

Python 16.81% R 0.02% Shell 0.01% TeX 0.09% Jupyter Notebook 81.74% Cython 1.33%
machine-learning economics causal-inference causality econometrics treatment-effects

econml's Introduction

Build status PyPI version PyPI wheel Supported Python versions

econml-logo EconML: A Python Package for ML-Based Heterogeneous Treatment Effects Estimation

EconML is a Python package for estimating heterogeneous treatment effects from observational data via machine learning. This package was designed and built as part of the ALICE project at Microsoft Research with the goal to combine state-of-the-art machine learning techniques with econometrics to bring automation to complex causal inference problems. The promise of EconML:

  • Implement recent techniques in the literature at the intersection of econometrics and machine learning
  • Maintain flexibility in modeling the effect heterogeneity (via techniques such as random forests, boosting, lasso and neural nets), while preserving the causal interpretation of the learned model and often offering valid confidence intervals
  • Use a unified API
  • Build on standard Python packages for Machine Learning and Data Analysis

One of the biggest promises of machine learning is to automate decision making in a multitude of domains. At the core of many data-driven personalized decision scenarios is the estimation of heterogeneous treatment effects: what is the causal effect of an intervention on an outcome of interest for a sample with a particular set of features? In a nutshell, this toolkit is designed to measure the causal effect of some treatment variable(s) T on an outcome variable Y, controlling for a set of features X, W and how does that effect vary as a function of X. The methods implemented are applicable even with observational (non-experimental or historical) datasets. For the estimation results to have a causal interpretation, some methods assume no unobserved confounders (i.e. there is no unobserved variable not included in X, W that simultaneously has an effect on both T and Y), while others assume access to an instrument Z (i.e. an observed variable Z that has an effect on the treatment T but no direct effect on the outcome Y). Most methods provide confidence intervals and inference results.

For detailed information about the package, consult the documentation at https://econml.azurewebsites.net/.

For information on use cases and background material on causal inference and heterogeneous treatment effects see our webpage at https://www.microsoft.com/en-us/research/project/econml/

Table of Contents

News

February 12, 2024: Release v0.15.0, see release notes here

Previous releases

November 11, 2023: Release v0.15.0b1, see release notes here

May 19, 2023: Release v0.14.1, see release notes here

November 16, 2022: Release v0.14.0, see release notes here

June 17, 2022: Release v0.13.1, see release notes here

January 31, 2022: Release v0.13.0, see release notes here

August 13, 2021: Release v0.12.0, see release notes here

August 5, 2021: Release v0.12.0b6, see release notes here

August 3, 2021: Release v0.12.0b5, see release notes here

July 9, 2021: Release v0.12.0b4, see release notes here

June 25, 2021: Release v0.12.0b3, see release notes here

June 18, 2021: Release v0.12.0b2, see release notes here

June 7, 2021: Release v0.12.0b1, see release notes here

May 18, 2021: Release v0.11.1, see release notes here

May 8, 2021: Release v0.11.0, see release notes here

March 22, 2021: Release v0.10.0, see release notes here

March 11, 2021: Release v0.9.2, see release notes here

March 3, 2021: Release v0.9.1, see release notes here

February 20, 2021: Release v0.9.0, see release notes here

January 20, 2021: Release v0.9.0b1, see release notes here

November 20, 2020: Release v0.8.1, see release notes here

November 18, 2020: Release v0.8.0, see release notes here

September 4, 2020: Release v0.8.0b1, see release notes here

March 6, 2020: Release v0.7.0, see release notes here

February 18, 2020: Release v0.7.0b1, see release notes here

January 10, 2020: Release v0.6.1, see release notes here

December 6, 2019: Release v0.6, see release notes here

November 21, 2019: Release v0.5, see release notes here.

June 3, 2019: Release v0.4, see release notes here.

May 3, 2019: Release v0.3, see release notes here.

April 10, 2019: Release v0.2, see release notes here.

March 6, 2019: Release v0.1, welcome to have a try and provide feedback.

Getting Started

Installation

Install the latest release from PyPI:

pip install econml

To install from source, see For Developers section below.

Usage Examples

Estimation Methods

Double Machine Learning (aka RLearner) (click to expand)
  • Linear final stage
from econml.dml import LinearDML
from sklearn.linear_model import LassoCV
from econml.inference import BootstrapInference

est = LinearDML(model_y=LassoCV(), model_t=LassoCV())
### Estimate with OLS confidence intervals
est.fit(Y, T, X=X, W=W) # W -> high-dimensional confounders, X -> features
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05) # OLS confidence intervals

### Estimate with bootstrap confidence intervals
est.fit(Y, T, X=X, W=W, inference='bootstrap')  # with default bootstrap parameters
est.fit(Y, T, X=X, W=W, inference=BootstrapInference(n_bootstrap_samples=100))  # or customized
lb, ub = est.effect_interval(X_test, alpha=0.05) # Bootstrap confidence intervals
  • Sparse linear final stage
from econml.dml import SparseLinearDML
from sklearn.linear_model import LassoCV

est = SparseLinearDML(model_y=LassoCV(), model_t=LassoCV())
est.fit(Y, T, X=X, W=W) # X -> high dimensional features
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05) # Confidence intervals via debiased lasso
  • Generic Machine Learning last stage
from econml.dml import NonParamDML
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier

est = NonParamDML(model_y=RandomForestRegressor(),
                  model_t=RandomForestClassifier(),
                  model_final=RandomForestRegressor(),
                  discrete_treatment=True)
est.fit(Y, T, X=X, W=W) 
treatment_effects = est.effect(X_test)
Dynamic Double Machine Learning (click to expand)
from econml.panel.dml import DynamicDML
# Use defaults
est = DynamicDML()
# Or specify hyperparameters
est = DynamicDML(model_y=LassoCV(cv=3), 
                 model_t=LassoCV(cv=3), 
                 cv=3)
est.fit(Y, T, X=X, W=None, groups=groups, inference="auto")
# Effects
treatment_effects = est.effect(X_test)
# Confidence intervals
lb, ub = est.effect_interval(X_test, alpha=0.05)
Causal Forests (click to expand)
from econml.dml import CausalForestDML
from sklearn.linear_model import LassoCV
# Use defaults
est = CausalForestDML()
# Or specify hyperparameters
est = CausalForestDML(criterion='het', n_estimators=500,       
                      min_samples_leaf=10, 
                      max_depth=10, max_samples=0.5,
                      discrete_treatment=False,
                      model_t=LassoCV(), model_y=LassoCV())
est.fit(Y, T, X=X, W=W)
treatment_effects = est.effect(X_test)
# Confidence intervals via Bootstrap-of-Little-Bags for forests
lb, ub = est.effect_interval(X_test, alpha=0.05)
Orthogonal Random Forests (click to expand)
from econml.orf import DMLOrthoForest, DROrthoForest
from econml.sklearn_extensions.linear_model import WeightedLasso, WeightedLassoCV
# Use defaults
est = DMLOrthoForest()
est = DROrthoForest()
# Or specify hyperparameters
est = DMLOrthoForest(n_trees=500, min_leaf_size=10,
                     max_depth=10, subsample_ratio=0.7,
                     lambda_reg=0.01,
                     discrete_treatment=False,
                     model_T=WeightedLasso(alpha=0.01), model_Y=WeightedLasso(alpha=0.01),
                     model_T_final=WeightedLassoCV(cv=3), model_Y_final=WeightedLassoCV(cv=3))
est.fit(Y, T, X=X, W=W)
treatment_effects = est.effect(X_test)
# Confidence intervals via Bootstrap-of-Little-Bags for forests
lb, ub = est.effect_interval(X_test, alpha=0.05)
Meta-Learners (click to expand)
  • XLearner
from econml.metalearners import XLearner
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor

est = XLearner(models=GradientBoostingRegressor(),
              propensity_model=GradientBoostingClassifier(),
              cate_models=GradientBoostingRegressor())
est.fit(Y, T, X=np.hstack([X, W]))
treatment_effects = est.effect(np.hstack([X_test, W_test]))

# Fit with bootstrap confidence interval construction enabled
est.fit(Y, T, X=np.hstack([X, W]), inference='bootstrap')
treatment_effects = est.effect(np.hstack([X_test, W_test]))
lb, ub = est.effect_interval(np.hstack([X_test, W_test]), alpha=0.05) # Bootstrap CIs
  • SLearner
from econml.metalearners import SLearner
from sklearn.ensemble import GradientBoostingRegressor

est = SLearner(overall_model=GradientBoostingRegressor())
est.fit(Y, T, X=np.hstack([X, W]))
treatment_effects = est.effect(np.hstack([X_test, W_test]))
  • TLearner
from econml.metalearners import TLearner
from sklearn.ensemble import GradientBoostingRegressor

est = TLearner(models=GradientBoostingRegressor())
est.fit(Y, T, X=np.hstack([X, W]))
treatment_effects = est.effect(np.hstack([X_test, W_test]))
Doubly Robust Learners (click to expand)
  • Linear final stage
from econml.dr import LinearDRLearner
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier

est = LinearDRLearner(model_propensity=GradientBoostingClassifier(),
                      model_regression=GradientBoostingRegressor())
est.fit(Y, T, X=X, W=W)
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05)
  • Sparse linear final stage
from econml.dr import SparseLinearDRLearner
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier

est = SparseLinearDRLearner(model_propensity=GradientBoostingClassifier(),
                            model_regression=GradientBoostingRegressor())
est.fit(Y, T, X=X, W=W)
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05)
  • Nonparametric final stage
from econml.dr import ForestDRLearner
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier

est = ForestDRLearner(model_propensity=GradientBoostingClassifier(),
                      model_regression=GradientBoostingRegressor())
est.fit(Y, T, X=X, W=W) 
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05)
Double Machine Learning with Instrumental Variables (click to expand)
  • Orthogonal instrumental variable learner
from econml.iv.dml import OrthoIV

est = OrthoIV(projection=False, 
              discrete_treatment=True, 
              discrete_instrument=True)
est.fit(Y, T, Z=Z, X=X, W=W)
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05) # OLS confidence intervals
  • Nonparametric double machine learning with instrumental variable
from econml.iv.dml import NonParamDMLIV

est = NonParamDMLIV(projection=False, 
                    discrete_treatment=True, 
                    discrete_instrument=True)
est.fit(Y, T, Z=Z, X=X, W=W) # no analytical confidence interval available
treatment_effects = est.effect(X_test)
Doubly Robust Machine Learning with Instrumental Variables (click to expand)
  • Linear final stage
from econml.iv.dr import LinearDRIV

est = LinearDRIV(discrete_instrument=True, discrete_treatment=True)
est.fit(Y, T, Z=Z, X=X, W=W)
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05) # OLS confidence intervals
  • Sparse linear final stage
from econml.iv.dr import SparseLinearDRIV

est = SparseLinearDRIV(discrete_instrument=True, discrete_treatment=True)
est.fit(Y, T, Z=Z, X=X, W=W)
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05) # Debiased lasso confidence intervals
  • Nonparametric final stage
from econml.iv.dr import ForestDRIV

est = ForestDRIV(discrete_instrument=True, discrete_treatment=True)
est.fit(Y, T, Z=Z, X=X, W=W)
treatment_effects = est.effect(X_test)
# Confidence intervals via Bootstrap-of-Little-Bags for forests
lb, ub = est.effect_interval(X_test, alpha=0.05) 
  • Linear intent-to-treat (discrete instrument, discrete treatment)
from econml.iv.dr import LinearIntentToTreatDRIV
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier

est = LinearIntentToTreatDRIV(model_y_xw=GradientBoostingRegressor(),
                              model_t_xwz=GradientBoostingClassifier(),
                              flexible_model_effect=GradientBoostingRegressor())
est.fit(Y, T, Z=Z, X=X, W=W)
treatment_effects = est.effect(X_test)
lb, ub = est.effect_interval(X_test, alpha=0.05) # OLS confidence intervals
Deep Instrumental Variables (click to expand)
import keras
from econml.iv.nnet import DeepIV

treatment_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_shape=(2,)),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(64, activation='relu'),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(32, activation='relu'),
                                    keras.layers.Dropout(0.17)])
response_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_shape=(2,)),
                                  keras.layers.Dropout(0.17),
                                  keras.layers.Dense(64, activation='relu'),
                                  keras.layers.Dropout(0.17),
                                  keras.layers.Dense(32, activation='relu'),
                                  keras.layers.Dropout(0.17),
                                  keras.layers.Dense(1)])
est = DeepIV(n_components=10, # Number of gaussians in the mixture density networks)
             m=lambda z, x: treatment_model(keras.layers.concatenate([z, x])), # Treatment model
             h=lambda t, x: response_model(keras.layers.concatenate([t, x])), # Response model
             n_samples=1 # Number of samples used to estimate the response
             )
est.fit(Y, T, X=X, Z=Z) # Z -> instrumental variables
treatment_effects = est.effect(X_test)

See the References section for more details.

Interpretability

Tree Interpreter of the CATE model (click to expand)
from econml.cate_interpreter import SingleTreeCateInterpreter
intrp = SingleTreeCateInterpreter(include_model_uncertainty=True, max_depth=2, min_samples_leaf=10)
# We interpret the CATE model's behavior based on the features used for heterogeneity
intrp.interpret(est, X)
# Plot the tree
plt.figure(figsize=(25, 5))
intrp.plot(feature_names=['A', 'B', 'C', 'D'], fontsize=12)
plt.show()

image

Policy Interpreter of the CATE model (click to expand)
from econml.cate_interpreter import SingleTreePolicyInterpreter
# We find a tree-based treatment policy based on the CATE model
intrp = SingleTreePolicyInterpreter(risk_level=0.05, max_depth=2, min_samples_leaf=1,min_impurity_decrease=.001)
intrp.interpret(est, X, sample_treatment_costs=0.2)
# Plot the tree
plt.figure(figsize=(25, 5))
intrp.plot(feature_names=['A', 'B', 'C', 'D'], fontsize=12)
plt.show()

image

SHAP values for the CATE model (click to expand)
import shap
from econml.dml import CausalForestDML
est = CausalForestDML()
est.fit(Y, T, X=X, W=W)
shap_values = est.shap_values(X)
shap.summary_plot(shap_values['Y0']['T0'])

Causal Model Selection and Cross-Validation

Causal model selection with the `RScorer` (click to expand)
from econml.score import RScorer

# split data in train-validation
X_train, X_val, T_train, T_val, Y_train, Y_val = train_test_split(X, T, y, test_size=.4)

# define list of CATE estimators to select among
reg = lambda: RandomForestRegressor(min_samples_leaf=20)
clf = lambda: RandomForestClassifier(min_samples_leaf=20)
models = [('ldml', LinearDML(model_y=reg(), model_t=clf(), discrete_treatment=True,
                             cv=3)),
          ('xlearner', XLearner(models=reg(), cate_models=reg(), propensity_model=clf())),
          ('dalearner', DomainAdaptationLearner(models=reg(), final_models=reg(), propensity_model=clf())),
          ('slearner', SLearner(overall_model=reg())),
          ('drlearner', DRLearner(model_propensity=clf(), model_regression=reg(),
                                  model_final=reg(), cv=3)),
          ('rlearner', NonParamDML(model_y=reg(), model_t=clf(), model_final=reg(),
                                   discrete_treatment=True, cv=3)),
          ('dml3dlasso', DML(model_y=reg(), model_t=clf(),
                             model_final=LassoCV(cv=3, fit_intercept=False),
                             discrete_treatment=True,
                             featurizer=PolynomialFeatures(degree=3),
                             cv=3))
]

# fit cate models on train data
models = [(name, mdl.fit(Y_train, T_train, X=X_train)) for name, mdl in models]

# score cate models on validation data
scorer = RScorer(model_y=reg(), model_t=clf(),
                 discrete_treatment=True, cv=3, mc_iters=2, mc_agg='median')
scorer.fit(Y_val, T_val, X=X_val)
rscore = [scorer.score(mdl) for _, mdl in models]
# select the best model
mdl, _ = scorer.best_model([mdl for _, mdl in models])
# create weighted ensemble model based on score performance
mdl, _ = scorer.ensemble([mdl for _, mdl in models])
First Stage Model Selection (click to expand)

First stage models can be selected either by passing in cross-validated models (e.g. sklearn.linear_model.LassoCV) to EconML's estimators or perform the first stage model selection outside of EconML and pass in the selected model. Unless selecting among a large set of hyperparameters, choosing first stage models externally is the preferred method due to statistical and computational advantages.

from econml.dml import LinearDML
from sklearn import clone
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV

cv_model = GridSearchCV(
              estimator=RandomForestRegressor(),
              param_grid={
                  "max_depth": [3, None],
                  "n_estimators": (10, 30, 50, 100, 200),
                  "max_features": (2, 4, 6),
              },
              cv=5,
           )
# First stage model selection within EconML
# This is more direct, but computationally and statistically less efficient
est = LinearDML(model_y=cv_model, model_t=cv_model)
# First stage model selection ouside of EconML
# This is the most efficient, but requires boilerplate code
model_t = clone(cv_model).fit(W, T).best_estimator_
model_y = clone(cv_model).fit(W, Y).best_estimator_
est = LinearDML(model_y=model_t, model_t=model_y)

Inference

Whenever inference is enabled, then one can get a more structure InferenceResults object with more elaborate inference information, such as p-values and z-statistics. When the CATE model is linear and parametric, then a summary() method is also enabled. For instance:

from econml.dml import LinearDML
# Use defaults
est = LinearDML()
est.fit(Y, T, X=X, W=W)
# Get the effect inference summary, which includes the standard error, z test score, p value, and confidence interval given each sample X[i]
est.effect_inference(X_test).summary_frame(alpha=0.05, value=0, decimals=3)
# Get the population summary for the entire sample X
est.effect_inference(X_test).population_summary(alpha=0.1, value=0, decimals=3, tol=0.001)
#  Get the parameter inference summary for the final model
est.summary()
Example Output (click to expand)
# Get the effect inference summary, which includes the standard error, z test score, p value, and confidence interval given each sample X[i]
est.effect_inference(X_test).summary_frame(alpha=0.05, value=0, decimals=3)

image

# Get the population summary for the entire sample X
est.effect_inference(X_test).population_summary(alpha=0.1, value=0, decimals=3, tol=0.001)

image

#  Get the parameter inference summary for the final model
est.summary()

image

Policy Learning

You can also perform direct policy learning from observational data, using the doubly robust method for offline policy learning. These methods directly predict a recommended treatment, without internally fitting an explicit model of the conditional average treatment effect.

Doubly Robust Policy Learning (click to expand)
from econml.policy import DRPolicyTree, DRPolicyForest
from sklearn.ensemble import RandomForestRegressor

# fit a single binary decision tree policy
policy = DRPolicyTree(max_depth=1, min_impurity_decrease=0.01, honest=True)
policy.fit(y, T, X=X, W=W)
# predict the recommended treatment
recommended_T = policy.predict(X)
# plot the binary decision tree
plt.figure(figsize=(10,5))
policy.plot()
# get feature importances
importances = policy.feature_importances_

# fit a binary decision forest
policy = DRPolicyForest(max_depth=1, min_impurity_decrease=0.01, honest=True)
policy.fit(y, T, X=X, W=W)
# predict the recommended treatment
recommended_T = policy.predict(X)
# plot the first tree in the ensemble
plt.figure(figsize=(10,5))
policy.plot(0)
# get feature importances
importances = policy.feature_importances_

image

To see more complex examples, go to the notebooks section of the repository. For a more detailed description of the treatment effect estimation algorithms, see the EconML documentation.

For Developers

You can get started by cloning this repository. We use setuptools for building and distributing our package. We rely on some recent features of setuptools, so make sure to upgrade to a recent version with pip install setuptools --upgrade. Then from your local copy of the repository you can run pip install -e . to get started (but depending on what you're doing you might want to install with extras instead, like pip install -e .[plt] if you want to use matplotlib integration, or you can use pip install -e .[all] to include all extras).

Running the tests

This project uses pytest for testing. To run tests locally after installing the package, you can use pip install pytest-runner followed by python setup.py pytest.

We have added pytest marks to some tests to make it easier to run a subset, and you can set the PYTEST_ADDOPTS environment variable to take advantage of this. For instance, you can set it to -m "not (notebook or automl)" to skip notebook and automl tests that have some additional dependencies.

Generating the documentation

This project's documentation is generated via Sphinx. Note that we use graphviz's dot application to produce some of the images in our documentation, so you should make sure that dot is installed and in your path.

To generate a local copy of the documentation from a clone of this repository, just run python setup.py build_sphinx -W -E -a, which will build the documentation and place it under the build/sphinx/html path.

The reStructuredText files that make up the documentation are stored in the docs directory; module documentation is automatically generated by the Sphinx build process.

Release process

We use GitHub Actions to build and publish the package and documentation. To create a new release, an admin should perform the following steps:

  1. Update the version number in econml/_version.py and add a mention of the new version in the news section of this file and commit the changes.
  2. Manually run the publish_package.yml workflow to build and publish the package to PyPI.
  3. Manually run the publish_docs.yml workflow to build and publish the documentation.
  4. Under https://github.com/py-why/EconML/releases, create a new release with a corresponding tag, and update the release notes.

Blogs and Publications

Citation

If you use EconML in your research, please cite us as follows:

Keith Battocchi, Eleanor Dillon, Maggie Hei, Greg Lewis, Paul Oka, Miruna Oprescu, Vasilis Syrgkanis. EconML: A Python Package for ML-Based Heterogeneous Treatment Effects Estimation. https://github.com/py-why/EconML, 2019. Version 0.x.

BibTex:

@misc{econml,
  author={Keith Battocchi, Eleanor Dillon, Maggie Hei, Greg Lewis, Paul Oka, Miruna Oprescu, Vasilis Syrgkanis},
  title={{EconML}: {A Python Package for ML-Based Heterogeneous Treatment Effects Estimation}},
  howpublished={https://github.com/py-why/EconML},
  note={Version 0.x},
  year={2019}
}

Contributing and Feedback

This project welcomes contributions and suggestions. We use the DCO bot to enforce a Developer Certificate of Origin which requires users to sign-off on their commits. This is a simple way to certify that you wrote or otherwise have the right to submit the code you are contributing to the project. Git provides a -s command line option to include this automatically when you commit via git commit.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the PyWhy Code of Conduct.

Community

pywhy-logo

EconML is a part of PyWhy, an organization with a mission to build an open-source ecosystem for causal machine learning.

PyWhy also has a Discord, which serves as a space for like-minded casual machine learning researchers and practitioners of all experience levels to come together to ask and answer questions, discuss new features, and share ideas.

We invite you to join us at regular office hours and community calls in the Discord.

References

Athey, Susan, and Stefan Wager. Policy learning with observational data. Econometrica 89.1, 133-161, 2021.

X Nie, S Wager. Quasi-Oracle Estimation of Heterogeneous Treatment Effects. Biometrika 108.2, 299-319, 2021.

V. Syrgkanis, V. Lei, M. Oprescu, M. Hei, K. Battocchi, G. Lewis. Machine Learning Estimation of Heterogeneous Treatment Effects with Instruments. Proceedings of the 33rd Conference on Neural Information Processing Systems (NeurIPS), 2019. (Spotlight Presentation)

D. Foster, V. Syrgkanis. Orthogonal Statistical Learning. Proceedings of the 32nd Annual Conference on Learning Theory (COLT), 2019. (Best Paper Award)

M. Oprescu, V. Syrgkanis and Z. S. Wu. Orthogonal Random Forest for Causal Inference. Proceedings of the 36th International Conference on Machine Learning (ICML), 2019.

S. Kรผnzel, J. Sekhon, J. Bickel and B. Yu. Metalearners for estimating heterogeneous treatment effects using machine learning. Proceedings of the national academy of sciences, 116(10), 4156-4165, 2019.

S. Athey, J. Tibshirani, S. Wager. Generalized random forests. Annals of Statistics, 47, no. 2, 1148--1178, 2019.

V. Chernozhukov, D. Nekipelov, V. Semenova, V. Syrgkanis. Plug-in Regularized Estimation of High-Dimensional Parameters in Nonlinear Semiparametric Models. Arxiv preprint arxiv:1806.04823, 2018.

S. Wager, S. Athey. Estimation and Inference of Heterogeneous Treatment Effects using Random Forests. Journal of the American Statistical Association, 113:523, 1228-1242, 2018.

Jason Hartford, Greg Lewis, Kevin Leyton-Brown, and Matt Taddy. Deep IV: A flexible approach for counterfactual prediction. Proceedings of the 34th International Conference on Machine Learning, ICML'17, 2017.

V. Chernozhukov, D. Chetverikov, M. Demirer, E. Duflo, C. Hansen, and a. W. Newey. Double Machine Learning for Treatment and Causal Parameters. ArXiv preprint arXiv:1608.00060, 2016.

Dudik, M., Erhan, D., Langford, J., & Li, L. Doubly robust policy evaluation and optimization. Statistical Science, 29(4), 485-511, 2014.

econml's People

Contributors

amarvenu avatar arose13 avatar danieldacosta avatar ewdillon avatar fverac avatar gaugup avatar greglewis avatar gregorybchris avatar heimengqi avatar imatiach-msft avatar jaronowitz avatar jcreinhold avatar jrfiedler avatar kbattocchi avatar kgao avatar microsoft-github-policy-service[bot] avatar microsoftopensource avatar moprescu avatar msftgits avatar mtanghu avatar olivertseng avatar padarn avatar stanton119 avatar star1327p avatar v-keacqu avatar v-shaal avatar vasilismsr avatar vsyrgkanis avatar xrowan avatar zjyfdu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

econml's Issues

get_params and set_params

We should implement get_params and set_params across all our cate estimators so that they can be used many times interchangeably with base estimators form sklearn. This will also help prettier copying and printing of the estimators.

shape of effects when X=None

When X is None, then const_marginal_effect() and effect() and marginal_effect() return a result that is of shape (1, d_y, d_t). The leading 1 seems weird to me. In this extreme case where the user wants average treatment effects, then we should not me adding the leading 1 in these results.

p-values, t-statistics and standard errors

Currently we only support an interface for getting intervals for target quantities. We might want to extend the api so as to return other quantities related to distributional properties of the estimated quantity, that might help in hypothesis testing. Examples include, p-values, t-statistics and standard errors.

Incorporation with SHAP

SHAP is a popular new model interpretability method. Seems to be gaining a lot of traction. Are there plans to incorporate these causal estimates with model agnostic interpretations? Purely out of curiosity, with only having read a little of your work

Asymptotic normal inference in OrthoForest

Since ortho forest is asymptotically normal, we can perform inference using the asymptotic normal confidence intervals and variance estimation. This would be computationally much more viable than bootstrapping the whole estimation. Also variance can be estimated via the bootstrap of little bags approach proposed in the GRF paper: https://arxiv.org/abs/1610.01271

Bootstrap Inference and Sample Weights

When using sample weights, the current bootstrap is no longer reasonable. Consider the case of one binary feature and were data are summarized and consist of means conditional on each feature value, with sample_weight corresponding to the number of original observations used to calculate the mean. Then our summarized data set consists of two samples each with weights. The bootstrap will be non-sense here as it will be drawing sometimes one sample twice or both samples. Thatโ€™s not the intention. We somehow need to simulate what bootstrap would have done in the original sample.

This is not possible simply with sample weights. Ideally we would need access to all original samples. One idea: also solicit sample variances (i.e. variances within segment); which will be stored in the sample_vars fit argument. Then what bootstrap can do is treat each segment as distributed from a normal with the given mean and variance. Then draw fresh random samples from that normal. Moreover the number of samples that we draw from each segment should be determined as follows: we draw n segment ids each with probability proportional to the original number of samples in that segment. Then for each of these segment samples we draw a y value drawn from the normal approximation of that segment.

For now, the package correctly throws a warning that โ€œ the bootstrap is used with sample weights and we advise against as the current implementation of bootstrap resampling does not take into account sample weights.โ€

Saving Models Using Pickle: NameError: name 'tf' is not defined

I'm struggling to save models using pickle.
The coding example I'm running

import cloudpickle
pickled = cp.dumps(self.deepIvEst)
cp.loads(pickled)

I got the error: NameError: name 'tf' is not defined

I get an error that finishes with the exception: NameError: name 'tf' is not defined. I added tf as a custom object then

File "/home/***/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/layers/__init__.py", line 168, in deserialize
    printable_module_name='layer')
  File "/home/***/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/utils/generic_utils.py", line 147, in deserialize_keras_object
    list(custom_objects.items())))
  File "/home/***/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/engine/network.py", line 1062, in from_config
    process_node(layer, node_data)
  File "/home/***/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/engine/network.py", line 1012, in process_node
    layer(unpack_singleton(input_tensors), **kwargs)
  File "/home/***/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/engine/base_layer.py", line 451, in __call__
    output = self.call(inputs, **kwargs)
  File "/home/***/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/layers/core.py", line 716, in call
    return self.function(inputs, **arguments)
  File "/home/***/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/econml/deepiv.py", line 159, in <lambda>
    samp = L.Lambda(lambda pms: _zero_grad(sample(*pms), pms), output_shape=(d_t,))
NameError: name '_zero_grad' is not defined

You may be wondering why I decided to start with the error: NameError: name 'tf' is not defined.

Well, it's because I wanted to highlight what I think the nature of this problem is. It's a scope issue. Everything that's imported to make the model works isn't used when unpickling a model. The big question is how to make this work?

One thing I'm thinking of is creating a separate function directly in each estimator that would allow us to reconstruct an entire model by using the hyperparameters and computed gradients, then re-embed them into the rest of the setup.

Results from GRF-resW

Hello,

I am working through the ORF paper (arXiv:1806.03467v2 [cs.LG] 12 Jul 2018) and I have a small question that I couldn't figure out on my own.

What are the results from the simulation when Y and T are residualized on W only and not on x? Is there a reason these aren't included in the paper but are in the R code GRF_treatment_effects.R?

Here is the snippet from the paper:

(3) GRF-Res: We perform a naive combination of double ML and GRF by first residualizing the treatments and outcomes on the both the features x and controls W , then running GRF R package on the residualized treatments Tห† , residualized outcomes Yห† , and features x.

and from the R code:
### Comparison #3
# Residualize on W, fit on x
forest <- causal_forest(input, data$res_Y_W, data$res_T_W)
tau_hat <- predict(forest, test_data)
results3[te_col] <- tau_hat$predictions

Thanks!

DR Learner crossfitting

The DoublyRobustLearner currently does not do crossfitting for nuisance estimation and estimates propensities and direct models on the whole dataset.

Identify subgroups.

Problem:
I think there should be some ways to identify subgroups suffer heterogeneous treatment effects. e.g tree structure can help us to tell that. It seems that SHAP targets only on 1-order (or maybe 2-order interaction), but what if we want more information, say higer orders?

Index Error in predicting CATE with LinearCateEstimator and DMLCateEstimator

I am facing an error on estimating conditional average treatment using the LinearCateEstimator(). I am following the example given in the documentation: https://econml.azurewebsites.net/spec/api.html#api-of-conditional-average-treatment-effect-package

Code:
est = LinearCateEstimator()
est.fit(y, T, X, W,Z)
print( est.effect(T0_test , T1_test, X))

est.effect(T0_test , T1_test, X) results in the following error:

IndexError Traceback (most recent call last)
in
1 est = LinearCateEstimator()
2 est.fit(y, T, X, W)
----> 3 print( est.effect(T0_test , T1_test, X))

~/.local/lib/python3.6/site-packages/econml/cate_estimator.py in effect(self, X, T0, T1)
148 # should it be possible to promote them to 2D arrays if that's what we saw during training?
149 eff = self.const_marginal_effect(X)
--> 150 m = shape(eff)[0]
151 if ndim(T0) == 0:
152 T0 = np.repeat(T0, m)

IndexError: tuple index out of range

Each of the T0_test, T1_test, X have the shape: (6500, 1)

I am not able to understand what leads to this error.

Also, I have faced issues in predicting CATE using DMLCateEstimator

est.effect(T0_test , T1_test, X) results in the following error for DMLCateEstimator:

python-input-30-ee269e75ba5f> in
1 est1 = DMLCateEstimator(model_y=LassoCV(), model_t=LassoCV())
2 est1.fit(Y, T, X, W)
----> 3 pred1= est1.effect(X, T0_test, T1_test)
4 print(pred1)
5 # est2 = DMLCateEstimator(model_y=RandomForestRegressor(),model_t=RandomForestRegressor())

~/.local/lib/python3.6/site-packages/econml/dml.py in effect(self, X, T0, T1)
137 T0 = self._one_hot_encoder.transform(reshape(self._label_encoder.transform(T0), (-1, 1)))[:, 1:]
138 T1 = self._one_hot_encoder.transform(reshape(self._label_encoder.transform(T1), (-1, 1)))[:, 1:]
--> 139 return super().effect(X, T0, T1)
140
141 def score(self, Y, T, X=None, W=None):

~/.local/lib/python3.6/site-packages/econml/cate_estimator.py in effect(self, X, T0, T1)
159 if ndim(eff) == ndim(dT): # y is a vector, rather than a 2D array
160 einsum_str = einsum_str.replace('y', '')
--> 161 return np.einsum(einsum_str, eff, dT)
162
163 def marginal_effect(self, T, X=None):

<array_function internals> in einsum(*args, **kwargs)

~/.local/lib/python3.6/site-packages/numpy/core/einsumfunc.py in einsum(*operands, **kwargs)
1354 # If no optimization, run pure einsum
1355 if optimize_arg is False:
-> 1356 return c_einsum(*operands, **kwargs)
1357
1358 valid_einsum_kwargs = ['out', 'dtype', 'order', 'casting']

ValueError: einstein sum subscripts string contains too many subscripts for operand 0

Update docs

Documentation is outdated for most of our estimators due to the several updates we have done on the code. We need to an overall update to our docs to sync with the current state.

Multiple discrete treatments in meta learners

All our meta learner classes and the DoublyRobustLearner can be extended in a straightforward manner to accept multiple discrete treatments and not only binary. We should enable such functionality.

Cross Price Elasticities

Dear all,

I have a problem implementing the cross price elasticity example. In particular I have a dataset consisting of an unbalanced panel of quantities sold of products and prices (so quantities sold and prices observed in time for different time slots each).

e.g. id_prod q_prod price Year

      1                  90             0.5        2001
      2                 10             3            2000
      2                 3              0.8          2001
      3.               23             2.9         2000   
      ...

I would like to recover the cross price elasticities: now, if I just use my "reshaped long" column of quantities as Y and the reshaped long column of prices as T, what I obtain is a single coefficient and NOT the matrix alpha whose I,j element is the elasticity of I when the price of j changes.

What I have done to obtain such a matrix has been to reshape the dataset in wide form and transpose it in order to obtain n (n = number of products) columns, one for each product and 14 rows, one for each year:
e.g. q_prod.1 q_prod.2 q_prodpr.3. price_1. price_2 price_3
2000 NaN. 10 23 NaN 3 2.9
2001. 90 3 7 0.5 0.8 0.6
2002. NaN NaN 110 NaN NaN 1.8

The code is below: y and t are reshaped data of quantities and prices; y_prova and t_prova represent the variables (I have taken a subset of products for computational speed) quantities and prices that I used as Y and T of the algorithm

y = pd.DataFrame(quantities_unstacked.T)
t = pd.DataFrame(prices_unstacked.T)
y_prova = y.iloc[0:13, 0:42648]
y_prova= np.array(y_prova, dtype=pd.DataFrame)
y_prova[pd.isnull(y_prova)] = 0

t_prova = t.iloc[0:13, 0:42648]
t_prova= np.array(t_prova, dtype=pd.DataFrame)
t_prova[pd.isnull(t_prova)] = 0

est = DMLCateEstimator(model_y=MultiTaskElasticNet(alpha=0.1),
model_t=MultiTaskElasticNet(alpha=0.1))
est.fit(y_prova, t_prova)

#a_hat[i,j] contains the elasticity of the demand of product i on the price of product j
a_hat = est.const_marginal_effect()
a_hat

The question is: is the procedure correct if I would like to obtain the cross elasticities price matrix (also taking into account of time dimension)? What if I would like to ignore NaN?

Sorry for the length of the question,

Thank you,

Federico

Standard errors

Dear all,

sorry for rising another issue.
I cannot see any documentation for computing the standard errors. Is there a way to do it?

I would like to obtain the significance level of the following:

est.const_marginal_effect(X_test_first_atc1.T)

Thank you all!

DeepIV Value Overflow

The following is a slightly modification to the DeepIV notebook, but it no longer works. I guess it is because of the fact that I increased the max value of the X and T, which caused the overflow somewhere:

from econml.deepiv import DeepIVEstimator
from econml.bootstrap import BootstrapEstimator
import keras
import numpy as np
import matplotlib.pyplot as plt

get_ipython().run_line_magic('matplotlib', 'inline')


# ## Synthetic data
# 
# To demonstrate the Deep IV approach, we'll construct a synthetic dataset obeying the requirements set out above.  In this case, we'll take `X`, `Z`, `T` to come from the following distribution: 

# In[ ]:


# Initialize exogenous variables; normal errors, uniformly distributed covariates and instruments
e = np.random.normal(size=(445,1))
x = np.random.uniform(low=0.0, high=1000, size=(445,1))
z = np.random.uniform(low=0.0, high=1000, size=(445,1))

# Initialize treatment variable
t = np.sqrt((x+2) * z) + e

# Show the marginal distribution of t
plt.hist(t)
plt.xlabel("t")
plt.show()

plt.scatter(z[x < 1], t[x < 1], label='low X')
plt.scatter(z[(x > 4.5) * (x < 5.5)], t[(x > 4.5) * (x < 5.5)], label='moderate X')
plt.scatter(z[x > 9], t[x > 9], label='high X')
plt.legend()
plt.xlabel("z")
plt.ylabel("t")
plt.show()


# Here, we'll imagine that `Z` and `X` are causally affecting `T`; as you can see in the plot above, low or high values of `Z` drive moderate values of `T` and moderate values of `Z` cause `T` to have a bi-modal distribution when `X` is high, but a unimodal distribution centered on 0 when `X` is low.  The instrument is positively correlated with the treatment and treatments tend to be bigger at high values of x. The instrument has higher power at higher values of x 

# In[ ]:


# Outcome equation 
y = t*t / 10 - x*t / 10 + e

# The endogeneity problem is clear, the latent error enters both treatment and outcome equally
plt.scatter(t,z, label ='raw data')
tticks = np.arange(-2,12)
yticks2 = tticks*tticks/10 - 0.2 * tticks
yticks5 = tticks*tticks/10 - 0.5 * tticks
yticks8 = tticks*tticks/10 - 0.8 * tticks
plt.plot(tticks,yticks2, 'r--', label = 'truth, x=2')
plt.plot(tticks,yticks5, 'g--', label = 'truth, x=5')
plt.plot(tticks,yticks8, 'y--', label = 'truth, x=8')
plt.xlabel("t")
plt.ylabel("y")
plt.legend()
plt.show()


# `Y` is a non-linear function of `T` and `X` with no direct dependence on `Z` plus additive noise (as required).  We want to estimate the effect of particular `T` and `X` values on `Y`.
# 
# The plot makes it clear that looking at the raw data is highly misleading as to the treatment effect. Moreover the treatment effects are both non-linear and heterogeneous in x, so this is a hard problem!
# 
# ## Defining the neural network models
# 
# Now we'll define simple treatment and response models using the Keras `Sequential` model built up of a series of layers.  Each model will have an `input_shape` of 2 (to match the sums of the dimensions of `X` plus `Z` in the treatment case and `T` plus `X` in the response case).

# In[ ]:


treatment_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_shape=(2,)),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(64, activation='relu'),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(32, activation='relu'),
                                    keras.layers.Dropout(0.17)])

response_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_shape=(2,)),
                                   keras.layers.Dropout(0.17),
                                   keras.layers.Dense(64, activation='relu'),
                                   keras.layers.Dropout(0.17),
                                   keras.layers.Dense(32, activation='relu'),
                                   keras.layers.Dropout(0.17),
                                   keras.layers.Dense(1)])


# Now we'll instantiate the `DeepIVEstimator` class using these models.  Defining the response model *outside* of the lambda passed into constructor is important, because (depending on the settings for the loss) it can be used multiple times in the second stage and we want the same weights to be used every time.

# In[ ]:


keras_fit_options = { "epochs": 30,
                      "validation_split": 0.1,
                      "callbacks": [keras.callbacks.EarlyStopping(patience=2, restore_best_weights=True)]}

deepIvEst = DeepIVEstimator(n_components = 10, # number of gaussians in our mixture density network
                            m = lambda z, x : treatment_model(keras.layers.concatenate([z,x])), # treatment model
                            h = lambda t, x : response_model(keras.layers.concatenate([t,x])),  # response model
                            n_samples = 1, # number of samples to use to estimate the response
                            use_upper_bound_loss = False, # whether to use an approximation to the true loss
                            n_gradient_samples = 1, # number of samples to use in second estimate of the response (to make loss estimate unbiased)
                            optimizer='adam', # Keras optimizer to use for training - see https://keras.io/optimizers/ 
                            first_stage_options = keras_fit_options, # options for training treatment model
                            second_stage_options = keras_fit_options) # options for training response model


# ## Fitting and predicting using the model
# Now we can fit our model to the data:

# In[ ]:


boot_est = BootstrapEstimator(deepIvEst, n_bootstrap_samples=2, n_jobs=1)
boot_est.fit(Y=y,T=t,X=x,Z=z)


# And now we can create a new set of data and see whether our predicted effect matches the true effect `T*T-X*X`:

# In[ ]:


n_test = 500
for i, x_enum in enumerate([2, 5, 8]):
    t = np.linspace(0,10,num = 100)
    y_true = t*t / 10 - x_enum*t/10
    y_pred = boot_est.predict(t, np.full_like(t, x_enum))
    plt.plot(t, y_true, label='true y, x={0}'.format(x_enum),color='C'+str(i))
    plt.plot(t, y_pred, label='pred y, x={0}'.format(x_enum),color='C'+str(i),ls='--')
plt.xlabel('t')
plt.ylabel('y')
plt.legend()
plt.show()


# You can see that despite the fact that the response surface varies with x, our model was able to fit the data reasonably well. Where is does worst is where the instrument has the least power, which is in the low x case.  There it fits a straight line rather than a quadratic, which suggests that the regularization at least is perfoming well.  

# ## Estimating the Confidence Interval of the Model

# In[ ]:


treatment_effect_interval = boot_est.marginal_effect_interval(X=x, T=t, lower=1, upper=99)


Could you help and investigate why?

WeightedLasso and WeightedLassoCV

If we enable sample weights in DML then we need to have a weighted lasso implementation and a weighted lasso CV implementation, where both the fitting and the cross validation splitting take into account the sample weights.

Ensure that we can use 1D inputs for Y and T

Right now, some estimators require that Y and T are 2D arrays, even when they just have a single column. The library would be more ergonomic if we allowed them to be 1D vectors as well.

Bootstrap and DeepIV does not work on Multiple Workers

I tried adding the bootstrap wrapper to the file Deep IV Examples.ipynb with n_jobs more than 1, but it gives me some errors:

deepIvEst = DeepIVEstimator(n_components = 10, # number of gaussians in our mixture density network
                            m = lambda z, x : treatment_model(keras.layers.concatenate([z,x])), # treatment model
                            h = lambda t, x : response_model(keras.layers.concatenate([t,x])),  # response model
                            n_samples = 1, # number of samples to use to estimate the response
                            use_upper_bound_loss = False, # whether to use an approximation to the true loss
                            n_gradient_samples = 1, # number of samples to use in second estimate of the response (to make loss estimate unbiased)
                            optimizer='adam', # Keras optimizer to use for training - see https://keras.io/optimizers/ 
                            s1=100, # number of epochs to train treatment model
                            s2=100) # number of epochs to train response model
boot_est = BootstrapEstimator(deepIvEst, n_bootstrap_samples=100, n_jobs=4)
boot_est.fit(Y=y,T=t,X=x,Z=z)
[Parallel(n_jobs=4)]: Using backend ThreadingBackend with 4 concurrent workers.
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-2528bf95d3ca> in <module>
----> 1 boot_est.fit(Y=y,T=t,X=x,Z=z)

~/Dropbox (Cambridge University)/R301/EconML/econml/bootstrap.py in fit(self, *args, **named_args)
     56         Parallel(n_jobs=self._n_jobs, prefer='threads', verbose=3)(
     57             (obj.fit, [arg[inds] for arg in args], {arg: named_args[arg][inds] for arg in named_args})
---> 58             for obj, inds in zip(self._instances, indices)
     59         )
     60         return self

~/anaconda3/lib/python3.6/site-packages/joblib-0.13.2-py3.6.egg/joblib/parallel.py in __call__(self, iterable)
    932 
    933             with self._backend.retrieval_context():
--> 934                 self.retrieve()
    935             # Make sure that we get a last message telling us we are done
    936             elapsed_time = time.time() - self._start_time

~/anaconda3/lib/python3.6/site-packages/joblib-0.13.2-py3.6.egg/joblib/parallel.py in retrieve(self)
    831             try:
    832                 if getattr(self._backend, 'supports_timeout', False):
--> 833                     self._output.extend(job.get(timeout=self.timeout))
    834                 else:
    835                     self._output.extend(job.get())

~/anaconda3/lib/python3.6/multiprocessing/pool.py in get(self, timeout)
    642             return self._value
    643         else:
--> 644             raise self._value
    645 
    646     def _set(self, i, obj):

~/anaconda3/lib/python3.6/multiprocessing/pool.py in worker(inqueue, outqueue, initializer, initargs, maxtasks, wrap_exception)
    117         job, i, func, args, kwds = task
    118         try:
--> 119             result = (True, func(*args, **kwds))
    120         except Exception as e:
    121             if wrap_exception and func is not _helper_reraises_exception:

~/anaconda3/lib/python3.6/site-packages/joblib-0.13.2-py3.6.egg/joblib/_parallel_backends.py in __call__(self, *args, **kwargs)
    565     def __call__(self, *args, **kwargs):
    566         try:
--> 567             return self.func(*args, **kwargs)
    568         except KeyboardInterrupt:
    569             # We capture the KeyboardInterrupt and reraise it as

~/anaconda3/lib/python3.6/site-packages/joblib-0.13.2-py3.6.egg/joblib/parallel.py in __call__(self)
    223         with parallel_backend(self._backend, n_jobs=self._n_jobs):
    224             return [func(*args, **kwargs)
--> 225                     for func, args, kwargs in self.items]
    226 
    227     def __len__(self):

~/anaconda3/lib/python3.6/site-packages/joblib-0.13.2-py3.6.egg/joblib/parallel.py in <listcomp>(.0)
    223         with parallel_backend(self._backend, n_jobs=self._n_jobs):
    224             return [func(*args, **kwargs)
--> 225                     for func, args, kwargs in self.items]
    226 
    227     def __len__(self):

~/Dropbox (Cambridge University)/R301/EconML/econml/deepiv.py in fit(self, Y, T, X, Z)
    313         n_components = self._n_components
    314 
--> 315         treatment_network = self._m(z_in, x_in)
    316 
    317         # the dimensionality of the output of the network

<ipython-input-10-df8c54d69989> in <lambda>(z, x)
      1 deepIvEst = DeepIVEstimator(n_components = 10, # number of gaussians in our mixture density network
----> 2                             m = lambda z, x : treatment_model(keras.layers.concatenate([z,x])), # treatment model
      3                             h = lambda t, x : response_model(keras.layers.concatenate([t,x])),  # response model
      4                             n_samples = 1, # number of samples to use to estimate the response
      5                             use_upper_bound_loss = False, # whether to use an approximation to the true loss

~/anaconda3/lib/python3.6/site-packages/keras/engine/base_layer.py in __call__(self, inputs, **kwargs)
    455             # Actually call the layer,
    456             # collecting output(s), mask(s), and shape(s).
--> 457             output = self.call(inputs, **kwargs)
    458             output_mask = self.compute_mask(inputs, previous_mask)
    459 

~/anaconda3/lib/python3.6/site-packages/keras/engine/network.py in call(self, inputs, mask)
    562             return self._output_tensor_cache[cache_key]
    563         else:
--> 564             output_tensors, _, _ = self.run_internal_graph(inputs, masks)
    565             return output_tensors
    566 

~/anaconda3/lib/python3.6/site-packages/keras/engine/network.py in run_internal_graph(self, inputs, masks)
    719                                     kwargs['mask'] = computed_mask
    720                             output_tensors = to_list(
--> 721                                 layer.call(computed_tensor, **kwargs))
    722                             output_masks = layer.compute_mask(computed_tensor,
    723                                                               computed_mask)

~/anaconda3/lib/python3.6/site-packages/keras/layers/core.py in call(self, inputs)
    877 
    878     def call(self, inputs):
--> 879         output = K.dot(inputs, self.kernel)
    880         if self.use_bias:
    881             output = K.bias_add(output, self.bias, data_format='channels_last')

~/anaconda3/lib/python3.6/site-packages/keras/backend/tensorflow_backend.py in dot(x, y)
   1083         out = tf.sparse_tensor_dense_matmul(x, y)
   1084     else:
-> 1085         out = tf.matmul(x, y)
   1086     return out
   1087 

~/anaconda3/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py in matmul(a, b, transpose_a, transpose_b, adjoint_a, adjoint_b, a_is_sparse, b_is_sparse, name)
   2385       are both set to True.
   2386   """
-> 2387   with ops.name_scope(name, "MatMul", [a, b]) as name:
   2388     if transpose_a and adjoint_a:
   2389       raise ValueError("Only one of transpose_a and adjoint_a can be True.")

~/anaconda3/lib/python3.6/site-packages/tensorflow/python/framework/ops.py in __enter__(self)
   6081       if self._values is None:
   6082         self._values = []
-> 6083       g = _get_graph_from_inputs(self._values)
   6084       self._g_manager = g.as_default()
   6085       self._g_manager.__enter__()

~/anaconda3/lib/python3.6/site-packages/tensorflow/python/framework/ops.py in _get_graph_from_inputs(op_input_list, graph)
   5711         graph = graph_element.graph
   5712       elif original_graph_element is not None:
-> 5713         _assert_same_graph(original_graph_element, graph_element)
   5714       elif graph_element.graph is not graph:
   5715         raise ValueError("%s is not from the passed-in graph." % graph_element)

~/anaconda3/lib/python3.6/site-packages/tensorflow/python/framework/ops.py in _assert_same_graph(original_item, item)
   5647   if original_item.graph is not item.graph:
   5648     raise ValueError("%s must be from the same graph as %s." % (item,
-> 5649                                                                 original_item))
   5650 
   5651 

ValueError: Tensor("dense_1/kernel:0", shape=(2, 128), dtype=float32_ref) must be from the same graph as Tensor("concatenate_1/concat:0", shape=(?, 2), dtype=float32).

However, the problem is solved when I change n_jobs=1:

boot_est = BootstrapEstimator(deepIvEst, n_bootstrap_samples=10, n_jobs=1)

Is this a currency issue (i.e. DeepIV can only be trained on one job)? But it shouldn't be because in Bootstrapping, you should be able to do parallel processing ...

DRLearner inference

We need to add a version of DoublyRobustLearner that has a linear final CATE model and supports inference via asymptotically normal statistics.

Pytorch Support

Will pytorch support be offered for this package?

I can think of a number of biomedical applications that could largely benefit from these approaches.

Make `T0` and `T1` on `effect` keyword-only to avoid confusion

When using inference, it's somewhat ambiguous whether a call like

est.effect_interval(X, low, high)

uses low and high as the optional treatments or as the lower and upper bounds of the confidence interval (the first interpretation is correct).

This confusion could be avoided by making T0 and T1 into keyword-only arguments, so that the user can call

est.effect_interval(X)
est.effect_interval(X, T0=low, T1=high)
est.effect_interval(X, lower=low, upper=high)

but not

est.effect_interval(X, low, high)

This would be a breaking change, but the safety benefits probably make it worthwhile anyway.

Allow users to reduce Deep IV verbosity

Right now when the neural networks are trained we're outputting a lot of info for each training epoch. We should find a way to give users more control (e.g. specifying extra keyword arguments for the training and response models' fit methods, which would let us set verbosity as well as other infrequently used arguments).

Second stage bootstrap

For orthogonal estimators that involve first stage nuisance estimation, we might want to add a lighter version of bootstrap inference that bootstraps only the computationally lighter second stage, while at the same time also being asymptotically valid.

Automated hyperparameter tuning of ortho forest parameters

Several hyperparameters in ortho forest should have a default setting that is data dependent and potentially set via some sophisticated method. We should enable such 'auto' defaults. Most important hyperparam seems to be the subsampling rate, which should definitely be set as a function of number of samples and features at fit time.

label:"help wanted" Matching Different Input Shapes

I'm running the following code to test different input shapes for DeepIV Estimator. I've been able to change input shapes via the input dimensions so far. The problem is that I've only been able to have a single dimension for my covariants, instruments, and treatments. I'm certain that's not the only way. I'm pretty sure the answer could be pretty simple, though I'm kind of jumping onto the deep-end with this library. I don't quite understand how.

To clarify the question is: How do I match different input shapes?

EconML Version: 0.4
Operating System: Ubuntu 18.10

Code that works:

from econml.deepiv import DeepIVEstimator
import keras
import numpy as np
import matplotlib.pyplot as plt
from count_service.primatives.base_models import treatment_model, response_model, create_callback

treatment_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_dim=8),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(64, activation='relu'),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(32, activation='relu'),
                                    keras.layers.Dropout(0.17)])

response_model = keras.Sequential([keras.layers.Dense(128, activation='relu', input_dim=8),
                                keras.layers.Dropout(0.17),
                                keras.layers.Dense(64, activation='relu'),
                                keras.layers.Dropout(0.17),
                                keras.layers.Dense(32, activation='relu'),
                                keras.layers.Dropout(0.17),
                                keras.layers.Dense(1)])


treatment_model2 = keras.Sequential([keras.layers.Dense(128, activation='relu', input_dim=2),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(64, activation='relu'),
                                    keras.layers.Dropout(0.17),
                                    keras.layers.Dense(32, activation='relu'),
                                    keras.layers.Dropout(0.17)])

response_model2 = keras.Sequential([keras.layers.Dense(128, activation='relu', input_dim=2),
                                keras.layers.Dropout(0.17),
                                keras.layers.Dense(64, activation='relu'),
                                keras.layers.Dropout(0.17),
                                keras.layers.Dense(32, activation='relu'),
                                keras.layers.Dropout(0.17),
                                keras.layers.Dense(1)])

n = 5000

# Initialize exogenous variables; normal errors, uniformly distributed covariates and instruments
e = np.random.normal(size=(n,1))
x = np.random.uniform(low=0.0, high=10.0, size=(n,1))
z = np.random.uniform(low=0.0, high=10.0, size=(n,1))

e_single = np.random.normal(size=(n,))
x_single = np.random.uniform(low=0.0, high=10.0, size=(n,))
z_single = np.random.uniform(low=0.0, high=10.0, size=(n,))

e_large = np.random.normal(size=(n,3))
x_large = np.random.uniform(low=0.0, high=10.0, size=(n,3))
z_large = np.random.uniform(low=0.0, high=10.0, size=(n,3))

t_single = np.sqrt((x_single+2) * z_single) + e_single

y_single = t_single*t_single / 10 - x_single*t_single / 10 + e_single


# Initialize treatment variable
t = np.sqrt((x+2) * z) + e

# Outcome equation 
y = t*t / 10 - x*t / 10 + e


t_large = np.sqrt((x_large+2) * z_large) + e_large
y_large = t_large*t_large / 10 - x_large*t_large / 10 + e_large

deepIvEst = DeepIVEstimator(n_components = 10, # number of gaussians in our mixture density network
                            m = lambda z, x : treatment_model2(keras.layers.concatenate([z,x])), # treatment model
                            h = lambda t, x : response_model2(keras.layers.concatenate([t,x])),  # response model
                            n_samples = 1, # number of samples to use to estimate the response
                            use_upper_bound_loss = False, # whether to use an approximation to the true loss
                            n_gradient_samples = 1, # number of samples to use in second estimate of the response (to make loss estimate unbiased)
                            optimizer='adam', # Keras optimizer to use for training - see https://keras.io/optimizers/ 
                            first_stage_options = create_callback(), # options for training treatment model
                            second_stage_options = create_callback()) # options for training response model




deepIvEst.fit(Y=y_single,T=t_single,X=x_single,Z=z_single)
for i, x in enumerate([2, 5, 8]):
    t = np.linspace((1, 4),(1, 20),num = 1000)
    y_true = t*t / 10 - x*t/10
    fuller = np.full_like(t, x)
    y_pred = deepIvEst.predict(fuller, np.full_like(fuller, x))

Code that doesn't work:

n = 5000

# Initialize exogenous variables; normal errors, uniformly distributed covariates and instruments
e = np.random.normal(size=(n,1))
x = np.random.uniform(low=0.0, high=10.0, size=(n,1))
z = np.random.uniform(low=0.0, high=10.0, size=(n,1))

e_single = np.random.normal(size=(n,))
x_single = np.random.uniform(low=0.0, high=10.0, size=(n,))
z_single = np.random.uniform(low=0.0, high=10.0, size=(n,))

e_large = np.random.normal(size=(n,3))
x_large = np.random.uniform(low=0.0, high=10.0, size=(n,3))
z_large = np.random.uniform(low=0.0, high=10.0, size=(n,3))

t_single = np.sqrt((x_single+2) * z_single) + e_single

y_single = t_single*t_single / 10 - x_single*t_single / 10 + e_single


# Initialize treatment variable
t = np.sqrt((x+2) * z) + e

# Outcome equation 
y = t*t / 10 - x*t / 10 + e


t_large = np.sqrt((x_large+2) * z_large) + e_large
y_large = t_large*t_large / 10 - x_large*t_large / 10 + e_large

deepIvEst = DeepIVEstimator(n_components = 10, # number of gaussians in our mixture density network
                            m = lambda z, x : treatment_model2(keras.layers.concatenate([z,x])), # treatment model
                            h = lambda t, x : response_model2(keras.layers.concatenate([t,x])),  # response model
                            n_samples = 1, # number of samples to use to estimate the response
                            use_upper_bound_loss = False, # whether to use an approximation to the true loss
                            n_gradient_samples = 1, # number of samples to use in second estimate of the response (to make loss estimate unbiased)
                            optimizer='adam', # Keras optimizer to use for training - see https://keras.io/optimizers/ 
                            first_stage_options = create_callback(), # options for training treatment model
                            second_stage_options = create_callback()) # options for training response model




deepIvEst.fit(Y=y_single,T=t_single,X=x_large,Z=z_large)
for i, x in enumerate([2, 5, 8]):
    t = np.linspace((1, 4),(1, 20),num = 1000)
    y_true = t*t / 10 - x*t/10
    fuller = np.full_like(t, x)
    y_pred = deepIvEst.predict(fuller, np.full_like(fuller, x))

I get this error (rightfully so)

Traceback (most recent call last):
  File "count_service/junk/direct_sample.py", line 81, in <module>
    deepIvEst.fit(Y=y_single,T=t_single,X=x_large,Z=z_large)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/econml/deepiv.py", line 323, in fit
    treatment_network = self._m(z_in, x_in)
  File "count_service/junk/direct_sample.py", line 69, in <lambda>
    m = lambda z, x : treatment_model2(keras.layers.concatenate([z,x])), # treatment model
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/engine/base_layer.py", line 451, in __call__
    output = self.call(inputs, **kwargs)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/engine/network.py", line 570, in call
    output_tensors, _, _ = self.run_internal_graph(inputs, masks)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/engine/network.py", line 727, in run_internal_graph
    layer.call(computed_tensor, **kwargs))
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/layers/core.py", line 908, in call
    output = K.dot(inputs, self.kernel)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/keras/backend/tensorflow_backend.py", line 1133, in dot
    out = tf.matmul(x, y)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/util/dispatch.py", line 180, in wrapper
    return target(*args, **kwargs)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/ops/math_ops.py", line 2647, in matmul
    a, b, transpose_a=transpose_a, transpose_b=transpose_b, name=name)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/ops/gen_math_ops.py", line 5925, in mat_mul
    name=name)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/framework/op_def_library.py", line 788, in _apply_op_helper
    op_def=op_def)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/util/deprecation.py", line 507, in new_func
    return func(*args, **kwargs)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/framework/ops.py", line 3616, in create_op
    op_def=op_def)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/framework/ops.py", line 2027, in __init__
    control_input_ops)
  File "/home/kevin/.local/share/virtualenvs/RayServices-dMeB-KyP/lib/python3.6/site-packages/tensorflow/python/framework/ops.py", line 1867, in _create_c_op
    raise ValueError(str(e))
ValueError: Dimensions must be equal, but are 6 and 2 for 'sequential_5/dense_15/MatMul' (op: 'MatMul') with input shapes: [?,6], [2,128].

The key difference here is what I'm training the program with.

I've been reading up on input shapes, and part of what I'm reading suggests that I might be approaching this problem wrong. I can get input shapes of equal size working. How should I approach this?

`BinaryCateEstimator` base class

We should create a BinaryCateEstimator base class that all estimators that solely accept binary treatments can inherit from. Alternatively we can create a DiscreteTreatmentCateEstimator which could also accept multiple discrete treatments. We could then make all our meta learners and the DoublyRobustLearner inherit from these classes. There are several things that can simplify in our API if we know that the cate estimator will only accept binary/discrete treatments and hence this can simplify development of child estimators for such classes.

WeightedModelWrapper issue with linear models with intercept

It might be that the weighted model wrapper has a small bug in the case of linear models that fit an intercept:

def _weighted_inputs(self, X, y, sample_weight):
    normalized_weights = X.shape[0] * sample_weight / np.sum(sample_weight)
    sqrt_weights = np.sqrt(normalized_weights)
    weight_mat = np.diag(sqrt_weights)
    return np.matmul(weight_mat, X), np.matmul(weight_mat, y)

If there is an intercept then we also want the intercept to be multiplied by the weight, so this transformation would be invalid. The user can fix it by augmenting X with an intercept and then calling a linear model without intercept. But this might be cumbersome.

verbosity levels

We might want to add verbosity params to most of our cate estimators which then each estimator can choose in an adhoc manner to print progress messages. This might be good since some methods might take time to run and progress messages can be good.

Panel wrapper for DML

Panel data have several intricacies that we might want to create a panel wrapper to our estimators that call our estimators in the right way when the data have a panel structure. We might want to add accompanying applications of the panel wrapper to pricing applications.

Causal Inference

I was wondering if the deepiv pacakge has causal inference built in? For example, in the causal random forest package, a confidence interval can be estimated for the treatment effects. Is there similar functionality here? So far I am only able to see a predicted value for treatment effect, but no standard errors.

Bootstrap

Dear all,

thanks again for this wonderful ML tool.
I would like to understand if it is possible, in my case to have t-statistics exploiting the bootstrap algorithm you provided. In particular I have employed both

est = DMLCateEstimator(model_y=MultiTaskElasticNet(alpha=0.1), model_t=MultiTaskElasticNet(alpha=0.1))and est = DMLCateEstimator(model_y=MultiTaskElasticNet(alpha=0.1), model_t=MultiTaskElasticNet(alpha=0.1),model_final=Lasso(alpha=0.1),featurizer=PolynomialFeatures(degree=1),random_state=123)
obtaining estimates for my Databases (15 in total). What I would like to do is computing SE or t-statistics directly by exploiting the bootstrap method you provided. My attempt was the following but I end up making a mess (basically I created a Y a T and an X suitable for the method and simply inputted in boot_est):

for i in range(1,16): df_part_q['df_part_q_'+str(i)] = pd.DataFrame() df_part_p['df_part_p_'+str(i)] = pd.DataFrame() Y['Y_'+ str(i)] = [] T['T_'+ str(i)] = [] X['X_'+ str(i)] = []

for j in range(1,16): df_part_q['df_part_q_'+str(j)] = pd.concat([quantities_unstacked['quantities_unstacked'+str(j)].T, quantities_unstacked_othertatc1['quantities_unstacked_otheratc1_'+str(j)].T], axis = 1)
df_part_p['df_part_p_'+str(j)] = pd.concat([prices_unstacked['prices_unstacked'+str(j)].T, prices_unstacked_othertatc1['prices_unstacked_otheratc1_'+str(j)].T], axis = 1) df_part_r['df_part_r_'+str(j)] = db['db_'+str(j)]['Recalls rest product']

for j in range(1,16): Y['Y_'+str(j)].append(df_part_q['df_part_q_'+str(j)].values) T['T_'+str(j)].append(df_part_p['df_part_p_'+str(j)].values) X['X_'+str(j)].append(df_part_r['df_part_r_'+str(j)].values)

from econml.bootstrap import BootstrapEstimator boot_est=BootstrapEstimator(DMLCateEstimator(model_y=MultiTaskElasticNetCV(cv=3),model_t=MultiTaskElasticNetCV(cv=3)) ,n_bootstrap_samples=20)

boot_estimate = {} for j in range(1,16): boot_estimate['boot_est'+str(j)] = [] for j in range(1,16): boot_estimate['boot_est'+str(j)].append(boot_est.fit(Y['Y_'+str(j)][0], T['T_'+str(j)][0].reshape(-1, 1)))

te_pred_interval = {} for j in range(1,16): te_pred_interval['te_pred_interval'+str(j)] = []

for j in range(1,16): te_pred_interval['te_pred_interval'+str(j)].append(boot_estimate['boot_est'+str(j)][0].const_marginal_effect())

Can you please help me in this? :)

Thank you,

Federico

Debiased Lasso inference in Double Machine Learning

When the final stage of a DMLCateEstimator is a high dimensional sparse linear model, then we can perform easier inference via the debiased lasso and using asymptotic normality. We need to implement a debiased lasso estimator that has inference capabilities (e.g. supports confidence intervals)

Handle the binary outcome in metalearners

Problem

When we use the ordinary classification models provided by sklearn, we get poor results because metalearners call the predict methods that return 0 or 1.

It would be because metalearners in EconML now focus on the continuous outcome.

Solution

I modified the predict methods in two ways:

  • create wrappers of sklearn models
  • override methods after the initialization of metalearners

I uploaded a jupyter notebook in gist to explain the modification.

Discussion and Proposition

I would like to find better solutions to handle the binary outcome (from my point of view, it would not be reasonable to add an option to handle the binary outcome in EconML right now). Do you have any ideas?

And I would appreciate it if you could provide a supplementary explanation about handling the binary outcome anywhere in your document.

Detect cases where model's predict shape differs from fit shape

The following example throws an exception because Lasso's predict returns a vector even when the input was a one-column array (originally reported as part of #85):

import numpy as np
from econml.dml import DMLCateEstimator
from sklearn.linear_model import Lasso

Y, T, X = [np.random.normal(size=(10,d)) for d in [1,1,2]]
DMLCateEstimator(model_t=Lasso(), model_y=Lasso()).fit(Y,T,X)

We ought to be able to identify this and reshape if necessary, either in DML or higher up in the class hierarchy.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.