A counterfactual explanation identifies the smallest changes to an input that would produce a different model outcome. For outlier detection, it shows the minimum change needed for an outlier record to become an inlier. section 13.2.4
Constructing a counterfactual explanation typically involves:
- Select an outlier, preferably one close to the inlier threshold, so the required changes are easier to interpret. section 13.2.4
- Prepare feature representations carefully: specify categorical and numeric variables, use ordinal encoding where appropriate, and scale values so column magnitudes are comparable. section 13.2.4
- Provide a probability-like prediction interface when the detector lacks predict_proba(), then generate several minimally changed alternatives. section 13.2.4
- Interpret the changed features as candidate causes, while remembering that counterfactuals may not fully reveal feature interactions or the detector’s complete logic. section 13.2.5
Abalone outlier
For an IsolationForest outlier in encoded and scaled abalone data, three counterfactuals alter only one feature at a time: Viscera_weight, Shucked_weight, or Length. Stronger outliers generally require changes to multiple features before becoming inliers. section 13.2.4
Listing 13.9 Generating counterfactual explanations using DiCE
import pandas as pd
import numpy as np
from sklearn.preprocessing import RobustScaler, OrdinalEncoder
from sklearn.datasets import fetch_openml
from sklearn.ensemble import IsolationForest
import dice_ml
np.random.seed(0)
data = fetch_openml('abalone', version=1, parser='auto') #1
df = pd.DataFrame(data.data, columns=data.feature_names)
df = pd.DataFrame(OrdinalEncoder().fit_transform(df), columns=df.columns)
df = pd.DataFrame(RobustScaler().fit_transform(df), columns=df.columns)
orig_features = list(df.columns)
det = IsolationForest() #2
det.fit(df)
df['IF Score'] = det.score_samples(df)
class IF_wrapper: #3
def __init__(self, det, cutoff):
self.det = det
self.cutoff = cutoff
def predict_proba(self, x):
return self.det.score_samples(x) < self.cutoff
if_wrapped = IF_wrapper(det, -0.6) #4
df['IF Outlier'] = df['IF Score'] < -0.6 #5
d = dice_ml.Data( #6
dataframe=df[orig_features + ['IF Outlier']],
continuous_features=['Sex', 'Length', 'Diameter', 'Height',
'Whole_weight', 'Shucked_weight',
'Viscera_weight'],
outcome_name='IF Outlier')
m = dice_ml.Model(model=if_wrapped, backend='sklearn')
exp = dice_ml.Dice(d, m, method='random')
query_instance = df.loc[2810:2810][orig_features]
dice_exp = exp.generate_counterfactuals(
query_instance, total_CFs=4, desired_class="opposite") #7
dice_exp.visualize_as_dataframe()
The DiCE listing demonstrates data preparation, IsolationForest wrapping, and counterfactual generation. section 13.2.4
