Skip to main content

Ruleset covering algorithms for explainable machine learning

Project description


And is there not also the case where we play and--make up the rules as we go along?
-Ludwig Wittgenstein

the duck-rabbit


This package implements two iterative coverage-based ruleset algorithms: IREP and RIPPERk.

Performance is similar to sklearn's DecisionTree CART implementation (see Performance Tests).

For explanation of the algorithms, see my article in Towards Data Science, or the papers below, under Useful References.


To install, use

$ pip install wittgenstein

To uninstall, use

$ pip uninstall wittgenstein


  • pandas
  • numpy
  • python version>=3.6


Usage syntax is similar to sklearn's.


Once you have loaded and split your data...

>>> import pandas as pd
>>> df = pd.read_csv(dataset_filename)
>>> from sklearn.model_selection import train_test_split # Or any other mechanism you want to use for data partitioning
>>> train, test = train_test_split(df, test_size=.33)

Use the fit method to train a RIPPER or IREP classifier:

>>> import wittgenstein as lw
>>> ripper_clf = lw.RIPPER() # Or irep_clf = lw.IREP() to build a model using IREP
>>>, class_feat='Party') # Or pass X and y data to .fit
>>> ripper_clf
<RIPPER with fit ruleset (k=2, prune_size=0.33, dl_allowance=64)> # Hyperparameter details available in the docstrings and TDS article below

Access the underlying trained model with the ruleset_ attribute, or output it with out_model(). A ruleset is a disjunction of conjunctions -- 'V' represents 'or'; '^' represents 'and'.

In other words, the model predicts positive class if any of the inner-nested condition-combinations are all true:

>>> ripper_clf.ruleset_
<Ruleset [physician-fee-freeze=n] V [synfuels-corporation-cutback=y^adoption-of-the-budget-resolution=y^anti-satellite-test-ban=n]>

IREP models tend be higher bias, RIPPER's higher variance.


To score our trained model, use the score function:

>>> X_test = test.drop(class_feat, axis=1)
>>> y_test = test[class_feat]
>>> ripper_clf.score(test_X, test_y)

Default scoring metric is accuracy. You can pass in alternate scoring functions, including those available through sklearn:

>>> from sklearn.metrics import precision_score, recall_score
>>> precision = clf.score(X_test, y_test, precision_score)
>>> recall = clf.score(X_test, y_test, recall_score)
>>> print(f'precision: {precision} recall: {recall}')
precision: 0.9914..., recall: 0.9953...

Model selection

wittgenstein is compatible with sklearn model_selection tools such as cross_val_score and GridSearchCV, as well as ensemblers like StackingClassifier.

Cross validation:

>>> # First dummify your categorical features and booleanize your class values to make sklearn happy
>>> X_train = pd.get_dummies(X_train, columns=X_train.select_dtypes('object').columns)
>>> y_train = x: 1 if x=='democrat' else 0)
>>> cross_val_score(ripper, X_train, y_train)

Grid search:

>>> param_grid = {"prune_size": [0.33, 0.5], "k": [1, 2]}
>>> grid = GridSearchCV(estimator=ripper, param_grid=param_grid)
>>>, y_train)


>>> tree = DecisionTreeClassifier(random_state=42)
>>> nb = GaussianNB(random_state=42)
>>> estimators = [("rip", ripper_clf), ("tree", tree), ("nb", nb)]
>>> ensemble_clf = StackingClassifier(estimators=estimators, final_estimator=LogisticRegression())
>>>, y_train)


To perform predictions, use predict:

>>> ripper_clf.predict(new_data)[:5]
[True, True, False, True, False]

Predict class probabilities with predict_proba:

>>> ripper_clf.predict_proba(test)
# Pairs of negative and positive class probabilities
array([[0.01212121, 0.98787879],
       [0.01212121, 0.98787879],
       [0.77777778, 0.22222222],
       [0.2       , 0.8       ],

We can also ask our model to tell us why it made each positive prediction using give_reasons:

>>> ripper_clf.predict(new_data[:5], give_reasons=True)
([True, True, False, True, True]
[<Rule [physician-fee-freeze=n]>],
[<Rule [physician-fee-freeze=n]>,
  <Rule [synfuels-corporation-cutback=y^adoption-of-the-budget-resolution=y^anti-satellite-test-ban=n]>], # This example met multiple sufficient conditions for a positive prediction
[<Rule object: [physician-fee-freeze=n]>],


If you encounter any issues, or if you have feedback or improvement requests for how wittgenstein could be more helpful for you, please post them to issues, and I'll respond.


Contributions are welcome! If you are interested in contributing, let me know at or on linkedin.

Useful references


->v0.2.3: 5/21/2020

  • Minor bugfixes and optimizations

v0.2.0: 5/4/2020

  • Algorithmic optimizations to improve training speed (~10x - ~100x)
  • Support for training on iterable datatypes besides DataFrames, such as numpy arrays and python lists
  • Compatibility with sklearn ensembling metalearners and sklearn model_selection
  • .predict_proba returns probas in neg, pos order
  • Certain parameters (hyperparameters, random_state, etc.) should now be passed into IREP/RIPPER constructors rather than the .fit method.
  • Sundry bugfixes

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for wittgenstein, version 0.2.3
Filename, size File type Python version Upload date Hashes
Filename, size wittgenstein-0.2.3-py3-none-any.whl (77.7 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size wittgenstein-0.2.3.tar.gz (11.3 MB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page