Skip to content

v-code01/cdintercept

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cdintercept

pyod's CD detector scores each point by Cook's distance, the influence of the point on a fitted regression. Cook's distance needs the leverage, the diagonal of the hat matrix of the design the model was fit on. CD fits each per-feature regression with LinearRegression(), whose default fit_intercept=True gives the design [1, X], but it computes the leverage from X alone. The hat-matrix trace equals the number of fitted parameters, so the correct leverages sum to n_predictors + 1; the ones from X sum to n_predictors, short by the intercept. Because the leverage enters Cook's distance nonlinearly through 1/(1 - h), the scores and the outlier ranking are wrong on the default path.

The code

pyod, pyod/models/cd.py, _Cooks_dist, 3.6.2 and current main (identical), line 38, with the model at line 161:

leverage = (X * np.linalg.pinv(X).T).sum(1)          # X alone, no intercept column
...
residuals = y - model.predict(X)                     # residuals from the intercept model
residuals_studentized = residuals / np.sqrt(mse) / np.sqrt(1 - leverage)
distance_ = residuals_studentized ** 2 / X.shape[1]
distance_ *= leverage / (1 - leverage)
# cd.py:161  self.model = model if model is not None else LinearRegression()   # fit_intercept=True

The residuals come from the intercept model, but the leverage comes from X without the intercept column, so the two are inconsistent. The correct leverage is the diagonal of the hat matrix of [1, X]. The fix is to build the leverage (and the rank and the / X.shape[1] divisor) from the intercept-augmented design when the model is fit with an intercept.

What it does

Cook's distance with the leverage from the model's own design or from X alone, on an off-center predictor with one high-influence point:

leverage sum with intercept: 2.0000 (= n_predictors + 1 = 2)
leverage sum from X alone:   1.0000 (= n_predictors = 1)
point 0 Cook's D: correct 1.9321, pyod 0.1915
max abs diff over all points: 1.7406

Driving the real library against statsmodels:

leverage sum: pyod 1.0000 (= n_predictors 1) vs correct 2.0000 (= n_predictors + 1)
point 0 Cook's D: correct 1.9321, pyod 0.1915 (understated 10.1x)
real _Cooks_dist ranking [0, 10, 5, 3, 1] vs statsmodels [0, 1, 10, 2, 3], differ True
fit_intercept=False matches statsmodels: True
real CD detector top-5: [0, 6, 10, 7, 14]

The hat-matrix trace is the number of fitted parameters, so the leverages must sum to 2 for a one-predictor model with an intercept; pyod's sum to 1. The most influential point's Cook's distance is 1.9321 by the statsmodels reference but 0.1915 from pyod, understated ten times over, and the real _Cooks_dist ranking reorders the second through fifth most-anomalous points relative to the correct ranking. The base case isolates the fault: with fit_intercept=False the X design is the right one and the real function matches statsmodels exactly, so the sole defect is the intercept mismatch.

Scope

The detector runs with default parameters, a default LinearRegression, and standard multi-feature input, and the fault fires whenever the features are not mean-centered, the normal case where the intercept is load-bearing. The failure is silent: an anomaly score and ranking are returned, but the influence of the actual outliers is understated and the ranking is corrupted, which for an outlier detector is the headline output. The error is a design-matrix inconsistency, not floating-point round-off, and the leverage enters nonlinearly so the min-max normalization does not cancel it. The fix is to compute the leverage from [1, X] when the model is fit with an intercept.

Layout

  • excerpt.py: the leverage line and the model construction quoted with the flags that name the fault, and the trace invariant.
  • cooks.py: Cook's distance with the leverage from [1, X] (correct, matches statsmodels) or from X alone (pyod), so the leverage sum is n_predictors + 1 or n_predictors and the distances differ.
  • consequence.py: the real _Cooks_dist and CD detector against statsmodels, the leverage sum, the understated outlier, the reordered ranking, and the fit_intercept=False base case that matches.
  • test_cdintercept.py: the model leverage sums differ by the intercept and the distances disagree, the real leverage sum is short, the outlier is understated, the ranking differs, and the no-intercept case matches.

Reproduce

python cooks.py
python consequence.py
python test_cdintercept.py

The leverage line is quoted from current main; the distances are produced by the real pyod _Cooks_dist and the CD detector against statsmodels. The fix is to build the leverage from the intercept-augmented design.

About

pyod's `CD` detector scores each point by Cook's distance, the influence of the point on a fitted regression. Cook's distance needs the leverage, the diagonal of the hat matrix of the design the model was fit on.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages