# cellMCD examples

## Raymaekers, J. and Rousseeuw, P.J.

### 2026-02-25

# Introduction

This file contains some examples of the use of the cellMCD function. It reproduces all the figures in the section on real data examples of the report “The cellwise MCD estimator” by J. Raymaekers and P.J. Rousseeuw.

    library("cellWise")

# Top Gear data

We chose this dataset because both its variables and its cases are fairly easy to interpret. We start by loading the data, select the numerical variables, and rename some of them for nicer visualizations.

    library(robustHD)
    data(TopGear)
    dim(TopGear)

    ## [1] 297  32

    rownames(TopGear) = paste(TopGear[,1],TopGear[,2])
    colnames(TopGear)

    ##  [1] "Maker"              "Model"              "Type"
    ##  [4] "Fuel"               "Price"              "Cylinders"
    ##  [7] "Displacement"       "DriveWheel"         "BHP"
    ## [10] "Torque"             "Acceleration"       "TopSpeed"
    ## [13] "MPG"                "Weight"             "Length"
    ## [16] "Width"              "Height"             "AdaptiveHeadlights"
    ## [19] "AdjustableSteering" "AlarmSystem"        "Automatic"
    ## [22] "Bluetooth"          "ClimateControl"     "CruiseControl"
    ## [25] "ElectricSeats"      "Leather"            "ParkingSensors"
    ## [28] "PowerSteering"      "SatNav"             "ESP"
    ## [31] "Verdict"            "Origin"

    TopGear = TopGear[c(5,7,9:17)] # objective numerical variables
    colnames(TopGear)

    ##  [1] "Price"        "Displacement" "BHP"          "Torque"       "Acceleration"
    ##  [6] "TopSpeed"     "MPG"          "Weight"       "Length"       "Width"
    ## [11] "Height"

    colnames(TopGear) = c("price", "displacement", "horsepower",
                          "torque", "acceleration", "top speed",
                          "miles/gallon", "weight", "length",
                          "width", "height")

Now we run the checkDataSet routine, to remove any observations with more than half of the variables missing. We additionally rescale the first variable to avoid huge numbers.

    out = checkDataSet(TopGear, silent = TRUE)

    ##
    ##  The final data set we will analyze has 295 rows and 11 columns.
    ##

    Xorig = out$remX
    dim(Xorig)

    ## [1] 295  11

    Xorig[,1] = Xorig[,1]/1000
    head(Xorig)

    ##                            price displacement horsepower torque acceleration
    ## Alfa Romeo Giulietta      21.250         1598        105    236         11.3
    ## Alfa Romeo MiTo           15.155         1368        105     95         10.7
    ## Aston Martin Cygnet       30.995         1329         98     92         11.8
    ## Aston Martin DB9         131.995         5935        517    457          4.6
    ## Aston Martin DB9 Volante 141.995         5935        517    457          4.6
    ## Aston Martin V12 Zagato  396.000         5935        510    420          4.2
    ##                          top speed miles/gallon weight length width height
    ## Alfa Romeo Giulietta           115           64   1385   4351  1798   1465
    ## Alfa Romeo MiTo                116           49   1090   4063  1720   1446
    ## Aston Martin Cygnet            106           56    988   3078  1680   1500
    ## Aston Martin DB9               183           19   1785   4720    NA   1282
    ## Aston Martin DB9 Volante       183           19   1890   4720    NA   1282
    ## Aston Martin V12 Zagato        190           17   1680   4385  1865   1250

Finally, we transform some variables (price, displacement, horsepower, torque, top speed) to get roughly gaussianity in the center:

    X = Xorig
    X[,c(1:4,6)] = log(Xorig[,c(1:4,6)])

There are still some NAs left in the data, most in weight:

    X = as.matrix(X)
    colSums(is.na(X))

    ##        price displacement   horsepower       torque acceleration    top speed
    ##            0            7            2            2            0            2
    ## miles/gallon       weight       length        width       height
    ##           10           31           10           15           10

Now the preprocessing is over, and we can start with analyzing the data further. First we standardize X to Z for marginal outliers. There are indeed quite a few marginal outliers, especially in the MPG variable

    out = estLocScale(X)
    Z = scale(X,center=out$loc, scale=out$scale)
    cutf = sqrt(qchisq(p=0.99,df=1))
    boxplot(Z)

plot of chunk unnamed-chunk-7

As a reference, we can use casewise MCD and inspect the resulting robust distances:

    out <- robustbase::covMcd(X)
    plot(sqrt(out$mah))
    abline(h = sqrt(qchisq(0.99, df = 11)))

plot of chunk unnamed-chunk-8

Now we run cellMCD:

    cellout <- cellMCD(X)

    ##
    ##  The input data has 295 rows and 11 columns.
    ## Objective at step 0 = 7961.68
    ## Objective at step 0 = 5023.3
    ## Objective at step 1 = 4811.56
    ## Objective at step 2 = 4755.17
    ## Objective at step 3 = 4712.72
    ## Objective at step 4 = 4686.52
    ## Objective at step 5 = 4676.54
    ## Objective at step 6 = 4675.52
    ## Objective at step 7 = 4675.14
    ## Objective at step 8 = 4676.68
    ## Objective at step 9 = 4677.32
    ## Objective at step 10 = 4676.23
    ## Objective at step 11 = 4674.77
    ## Objective at step 12 = 4678.39
    ## Objective at step 13 = 4677.49
    ## Objective at step 14 = 4674.78
    ## Objective at step 15 = 4675.29
    ## Objective at step 16 = 4673.08
    ## Objective at step 17 = 4678
    ## Objective at step 18 = 4678.88
    ## Objective at step 19 = 4679.17
    ## Objective at step 20 = 4679.27
    ## Objective at step 21 = 4679.31
    ## Objective at step 22 = 4679.32
    ## Percentage of flagged cells per variable:
    ##        price displacement   horsepower       torque acceleration    top.speed
    ##        13.56        10.85         5.42         3.73         6.78        12.88
    ## miles.gallon       weight       length        width       height
    ##         9.83        18.31         9.83        12.88         5.76

We immediately see 3 huge residuals in MPG, and one in acceleration.

    Zres = cellout$Zres
    boxplot(Zres)

plot of chunk unnamed-chunk-10

    qqnorm(as.vector(Zres)); abline(0,1)

plot of chunk unnamed-chunk-10

How many cells were flagged by cellMCD? In total, 324 cells were flagged. We also see that 151 cars do not have a single flagged cell.

    W = cellout$W
    sum(as.vector(1-W))

    ## [1] 324

    rowS = rowSums(1-W)
    sum(rowS == 0) # 151 cars do not have a single flagged cell.

    ## [1] 151

Now look at some plots by variable. We start with price:

    j = 1 # price
    # Index plot of Zres:
    # (ids = plot.cellMCD(cellout, j, type="index", identify=T)$ids)
    ids = c(6,54,50,195,212,222,221)
    Zres[ids,j,drop=F]

    ##                               price
    ## Aston Martin V12 Zagato   12.119350
    ## Chevrolet Camaro          -5.803788
    ## Bugatti Veyron            11.886160
    ## Pagani Huayra             12.344510
    ## Proton Satria-Neo         -5.981361
    ## Rolls-Royce Phantom Coupe  9.780492
    ## Rolls-Royce Phantom        9.976976

    plot_cellMCD(cellout, j, type="index", ids=ids)

plot of chunk unnamed-chunk-12

We saw that price has quite a few outliers on the right. Camaro and Proton cost less than would be expected based on their other characteristics.

    # Plot Zres versus X:
    plot_cellMCD(cellout, j, type="Zres/X", ids=ids)

plot of chunk unnamed-chunk-13

    # Plot Zres versus predicted cell:
    plot_cellMCD(cellout, j, type="Zres/pred", ids=ids)

plot of chunk unnamed-chunk-13

    # Plot observed cell versus predicted cell:
    # (ids = plot.cellMCD(cellout, j, type="X/pred", identify=T)$ids)
    ids = c(179,3,212,218,54,6,222,221,50,195)
    Xorig[ids,j,drop=F]

    ##                              price
    ## Mitsubishi i-MiEV           29.045
    ## Aston Martin Cygnet         30.995
    ## Proton Satria-Neo            8.495
    ## Renault Twizy                6.950
    ## Chevrolet Camaro            39.995
    ## Aston Martin V12 Zagato    396.000
    ## Rolls-Royce Phantom Coupe  333.130
    ## Rolls-Royce Phantom        352.720
    ## Bugatti Veyron            1139.985
    ## Pagani Huayra              990.000

    plot_cellMCD(cellout, j, type="X/pred", vband=T, hband=T, ids=ids)

plot of chunk unnamed-chunk-13

The code below reproduces Figure 2 of the paper.

    ###########
    ## Figure 2
    ###########

    # pdf(file="TopGear_fig2_test.pdf",width=5.2,height=10.4)
    par(mfrow=c(2,1)) # (nrows,ncols)
    rn = rownames(X) # for object labels
    #
    ######### 1. HP
    par(mar = c(3.6, 3.5, 2.8, 1.0))
    j = 3
    ids = c(52,59,70,218)
    labs = rn[ids]; labs[1] = "Caterham"
    xvals = c(52, 59, 70, 218)
    yvals = c(-6.20, -8.35, -4.55, -4.8)
    plot_cellMCD(cellout, j, type="index",
                 ylab="standardized residual of log(horsepower)",
                 main="standardized residual of log(horsepower)")
    text(x = xvals, y = yvals, labels = labs, cex = 0.9)
    #
    ######### 2. Length
    par(mar = c(3.6, 3.5, 2.8, 1.0))
    #
    j = 9
    ids = c(3,50,51,119,144,195,218,232,249)
    labs = rn[ids]
    labs[1] = "Aston Cygnet"; labs[3] = "Caterham"
    xvals = c(2560, 5050, 3100, 5252, 4245, 4605, 2360, 2700, 3330)
    yvals = c(-4.46, -4.21, -3.91, 4.20, -4.67, -5.43,
              -5.46, -6.45, -5.26)
    plot_cellMCD(cellout, j, type="Zres/X",
                 main="standardized residual versus X for length",
                 xlab="length (mm)", xlim=c(1970,6000),
                 ylim=c(-6.51,4.88)) #,ids=ids)
    text(x = xvals, y = yvals, labels = labs, cex = 0.9)

plot of chunk unnamed-chunk-14

    # dev.off()

Now we reproduce Figure 3.

    # pdf(file="TopGear_fig3_test.pdf",width=5.2,height=10.4)
    par(mfrow=c(2,1))
    rn = rownames(X) # for object labels
    #
    ######### 1. weight
    par(mar = c(3.6, 3.5, 2.8, 1.0))
    j = 8
    ids = c(29,51,144,163,183,197,195)
    pred = cellout$preds
    labs = rn[ids]
    labs[2] = "Caterham"
    labs[4] = "Mercedes-Benz G"
    xvals = c(1490, 1000, 1890, 1575, 1757,  810, 2250)
    yvals = c(5.63,-5.07,-5.10,4.52,-6.19,-6.60,-8.20)
    plot_cellMCD(cellout, j, type="Zres/pred", # vband=F,
                 xlab = "predicted weight (kg)") # , ids=ids)
    text(x = xvals, y = yvals, labels = labs, cex = 0.9)
    #
    ######### 2. top speed
    par(mar = c(3.6, 3.5, 2.8, 1.0))
    j = 6
    ids = c(50,42,52,79,195,218,219,232,258)
    labs = rn[ids]
    labs[3] = "Caterham"
    xvals = c(5.145, 4.937, 5.083, 5.065, 5.076, 4.90, 4.75, 4.63, 5.038)
    yvals = c(5.53, 4.53, 4.72, 5.345, 5.44, 3.97, 4.43, 4.30, 4.61)
    plot_cellMCD(cellout, j, type="X/pred", vband=F, vlines=F,
                 xlab="predicted log(top speed)",
                 ylab="observed log(top speed)",
                 main="log(top speed) versus its prediction") #, ids=ids)
    text(x = xvals, y = yvals, labels = labs, cex = 0.9)

plot of chunk unnamed-chunk-15

    # dev.off()

Finally, we create Figure 4.

    ############
    ### Figure 4
    ############

    # pdf(file="TopGear_fig4_test.pdf",width=5.2,height=10.4)
    par(mfrow=c(2,1)) # (nrows,ncols)
    rn = rownames(X) # for object labels
    #
    ######### 1. MPG versus torque
    par(mar = c(3.6, 3.5, 2.8, 1.0))
    ids=c(42,258,50)
    labs = rn[ids]
    xvals = c(5.56, 6.28, 7.27)
    yvals = c(470, 235, -16)
    plot_cellMCD(cellout, type = "bivariate", horizvar = 4,
                 vertivar = 7, ids = ids,
                 xlim=c(3.5,7.7), ylim = c(-27,480),
                 main = "miles/gallon versus log(torque)",
                 xlab = "log(torque)",
                 ylab = "miles/gallon",
                 opacity=0.5, labelpoints=F)
    text(x = xvals, y = yvals, labels = labs, cex = 0.9)
    #
    ######### 2. Width versus acceleration
    par(mar = c(3.6, 3.5, 2.8, 1.0))
    ids = c(218,144,233,51,136,179)
    labs = rn[ids]
    labs[3] = "Ssangyong" # name was too long for plot
    labs[4] = "Caterham"
    labs[5] = "Land Rover"
    xvals = c(0.0,  -3.0, -3.23,  0.5, 14.2, 15.9)
    yvals = c(1200, 1850, 1915, 1688, 2039, 1445)
    plot_cellMCD(cellout,  type = "bivariate", horizvar=5,
                 vertivar=10, ids=ids,
                 xlab = "acceleration (seconds to 62 mph)",
                 ylab = "width (mm)", xlim = c(-6.1,20),
                 ylim = c(1200, 2150),
                 opacity=0.5, labelpoints=F)
    text(x = xvals, y = yvals, labels = labs, cex = 0.9)

plot of chunk unnamed-chunk-16

    # dev.off()

    par(mfrow=c(1,1))

# Comparison of cellMCD with covMCD

In the following, we compare covMCD and cellMCD on benchmark datasets with casewise outliers. Since some datasets have skewed variables, we always run X = transfo(X)$Y before both estimators. We require n/d >= 5, and we will stop if some variables have 25% or more outliers. The easiest way to compare both fits is to plot(rd,cd) and report cor(rd,cd).

The robustbase library contains the datasets as well as the covMCD function.

    library(robustbase)

## Aircraft data without response (n=23, d=4)

In this dataset we obtain a very high correlation (over 95%) between the robust distances based on covMCD and cellMCD.

    data(aircraft)
    X      <- as.matrix(aircraft)[, 1:4]
    pairs(X)

plot of chunk unnamed-chunk-18

    X      <- transfo(X)$Y

    ##
    ##  The input data has 23 rows and 4 columns.

    rowout <- robustbase::covMcd(X)
    rd     <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975,ncol(X)))
    sum(rd > cutoff)

    ## [1] 2

    cellout <- cellMCD(X)

    ##
    ##  The input data has 23 rows and 4 columns.
    ## Objective at step 0 = 279.823
    ## Objective at step 0 = 165.257
    ## Objective at step 1 = 152.977
    ## Objective at step 2 = 153.282
    ## Objective at step 3 = 153.811
    ## Objective at step 4 = 154.078
    ## Objective at step 5 = 154.185
    ## Objective at step 6 = 154.226
    ## Objective at step 7 = 154.242
    ## Percentage of flagged cells per variable:
    ##    X1    X2    X3    X4
    ##  8.70 21.74 17.39  4.35

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    plot(rd, cd)

plot of chunk unnamed-chunk-18

    round(cor(rd, cd),3)

    ## [1] 0.953

## Aircraft with response (n=23, d=5)

When we add the response to the Aircraft dataset, we have n/d < 5. Therefore, the data is no longer suitable for cellMCD.

    data(aircraft)
    X <- as.matrix(aircraft)
    nrow(X) / ncol(X)

    ## [1] 4.6

There is also another issue. The response variable has more than 25% marginal outliers. This leads to an error

    cellout <- cellMCD(X)

    ##
    ##  The input data has 23 rows and 5 columns.

    ## Warning in cellMCD(X): There are fewer than 5 cases per dimension in this data set.
    ## It is not recommended to run cellMCD on these data.
    ## Consider reducing the number of variables.

    ##
    ## At least one variable of X has more than 100*(1-alpha)% = 25%
    ## of marginal outliers.
    ## The percentages per variable are:
    ##    X1    X2    X3    X4     Y
    ##  0.00  4.35 13.04  4.35 30.43

    ## Error in cellMCD(X): Too many marginal outliers.

## alcohol (n=44, d=7)

Here we obtain a correlation between rd and cd of close to 90%. Note that covMCD has a very small eigenvalue (of the order 1e-7). The cellMCD hits its lower bound on the eigenvalues and is thus regularized.

    data(alcohol)
    X <- as.matrix(alcohol)
    X <- transfo(X)$Y

    ##
    ##  The input data has 44 rows and 7 columns.

    rowout <- robustbase::covMcd(X)
    eigen(rowout$cov)$values

    ## [1] 7.060114e+00 5.718620e-02 1.003412e-02 3.760821e-03 8.303637e-05
    ## [6] 8.709933e-06 2.645564e-07

    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 16

    cellout <- cellMCD(X)

    ##
    ##  The input data has 44 rows and 7 columns.
    ## Objective at step 0 = -438.598
    ## Objective at step 0 = -739.338
    ## Objective at step 1 = -781.081
    ## Objective at step 2 = -789.705
    ## Objective at step 3 = -783.09
    ## Objective at step 4 = -778.843
    ## Objective at step 5 = -776.897
    ## Objective at step 6 = -759.094
    ## Objective at step 7 = -736.309
    ## Objective at step 8 = -723.714
    ## Objective at step 9 = -717.058
    ## Objective at step 10 = -713.642
    ## Objective at step 11 = -711.895
    ## Objective at step 12 = -710.995
    ## Objective at step 13 = -710.529
    ## Percentage of flagged cells per variable:
    ##           SAG             V         logPC             P            RM
    ##          2.27          0.00         13.64          0.00          2.27
    ##          Mass logSolubility
    ##          0.00         18.18

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd),3)

    ## [1] 0.851

## Animals2 (n=65, d=2)

The original Animals dataset (n=28) is in package MASS and gives a very similar result.

    data(Animals2)
    X <- as.matrix(log(Animals2))
    X <- transfo(X)$Y

    ##
    ##  The input data has 65 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cellout <- cellMCD(X)

    ##
    ##  The input data has 65 rows and 2 columns.
    ## Objective at step 0 = 322.457
    ## Objective at step 0 = 204.291
    ## Objective at step 1 = 201.749
    ## Objective at step 2 = 202.46
    ## Objective at step 3 = 202.446
    ## Objective at step 4 = 202.446
    ## Objective at step 5 = 202.446
    ## Percentage of flagged cells per variable:
    ##  body brain
    ##  9.23  1.54

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    plot(rd, cd)

plot of chunk unnamed-chunk-22

    round(cor(rd, cd), 3)

    ## [1] 0.946

## bushfire (n=38, d=5)

This dataset yields again a very high correlation between rd and cd of over 97%.

    data(bushfire)
    X <- as.matrix(bushfire)
    X <- transfo(X)$Y

    ##
    ##  The input data has 38 rows and 5 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X,center=rowout$center,cov=rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 17

    cellout <- cellMCD(X)

    ##
    ##  The input data has 38 rows and 5 columns.
    ## Objective at step 0 = 749.598
    ## Objective at step 0 = 223.352
    ## Objective at step 1 = 182.075
    ## Objective at step 2 = 175.391
    ## Objective at step 3 = 216.337
    ## Objective at step 4 = 279.456
    ## Objective at step 5 = 306.09
    ## Objective at step 6 = 324.374
    ## Objective at step 7 = 365.949
    ## Objective at step 8 = 374.454
    ## Objective at step 9 = 377.928
    ## Objective at step 10 = 379.75
    ## Objective at step 11 = 380.807
    ## Objective at step 12 = 381.443
    ## Objective at step 13 = 381.831
    ## Objective at step 14 = 382.069
    ## Objective at step 15 = 382.216
    ## Percentage of flagged cells per variable:
    ##    V1    V2    V3    V4    V5
    ##  2.63  0.00 13.16 13.16 15.79

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.979

## cloud (n = 19, d = 2)

This small dataset does not really contain outliers. No cells are flagged, and the results of covMCD and cellMCD are very similar

    data(cloud)
    X <- as.matrix(cloud)
    X <- transfo(X)$Y

    ##
    ##  The input data has 19 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cellout <- cellMCD(X)

    ##
    ##  The input data has 19 rows and 2 columns.
    ## Objective at step 0 = 5.6568
    ## Objective at step 0 = 4.15
    ## Objective at step 1 = 4.15
    ## Percentage of flagged cells per variable:
    ## Percentage CloudPoint
    ##          0          0

    cd <- sqrt(mahalanobis(X,center=cellout$mu,cov=cellout$S))
    round(cor(rd,cd),3)

    ## [1] 0.97

## delivery without response n=25, d=2

Another example without real outliers. The results of cellMCD and covMCD are very close.

    data(delivery)
    X <- as.matrix(delivery)[, 1:2]
    X <- transfo(X)$Y

    ##
    ##  The input data has 25 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 0

    cellout <- cellMCD(X)

    ##
    ##  The input data has 25 rows and 2 columns.
    ## Objective at step 0 = 141.795
    ## Objective at step 0 = 130.433
    ## Objective at step 1 = 130.433
    ## Percentage of flagged cells per variable:
    ##   n.prod distance
    ##        0        0

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.952

## delivery with response n=25, d=3

Now there is a small difference between cellMCD and covMCD. The output of cellMCD is more informative, pointing to the likely source of outlyingness.

    data(delivery)
    X <- as.matrix(delivery)
    X <- transfo(X)$Y

    ##
    ##  The input data has 25 rows and 3 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 6

    cellout <- cellMCD(X)

    ##
    ##  The input data has 25 rows and 3 columns.
    ## Objective at step 0 = 152.867
    ## Objective at step 0 = 137.002
    ## Objective at step 1 = 136.961
    ## Objective at step 2 = 136.961
    ## Objective at step 3 = 136.962
    ## Objective at step 4 = 136.962
    ## Percentage of flagged cells per variable:
    ##   n.prod distance  delTime
    ##        4        0        0

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.67

## exAM (n=12, d=2)

Very similar result between covMCD and cellMCD in terms of location and covariance estimates, and only one suspicious observation is identified. The cellMCD points to the most likely source (i.e. cell) causing the outlyingness of this observation.

    data(exAM)
    X <- as.matrix(exAM)
    X <- transfo(X)$Y

    ##
    ##  The input data has 12 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 1

    cellout <- cellMCD(X)

    ##
    ##  The input data has 12 rows and 2 columns.
    ## Objective at step 0 = 53.6344
    ## Objective at step 0 = 51.5032
    ## Objective at step 1 = 51.4928
    ## Objective at step 2 = 51.4931
    ## Objective at step 3 = 51.4932
    ## Percentage of flagged cells per variable:
    ##    x    y
    ## 8.33 0.00

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.984

## hbk without response (n=75, d=3)

Here we find perfect correlation between rd and cd.

    data(hbk)
    X <- as.matrix(hbk)[, 1:3]
    X <- transfo(X)$Y

    ##
    ##  The input data has 75 rows and 3 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 14

    cellout <- cellMCD(X)

    ##
    ##  The input data has 75 rows and 3 columns.
    ## Objective at step 0 = 755.379
    ## Objective at step 0 = 750.629
    ## Objective at step 1 = 750.469
    ## Objective at step 2 = 750.464
    ## Objective at step 3 = 750.466
    ## Objective at step 4 = 750.467
    ## Objective at step 5 = 750.468
    ## Objective at step 6 = 750.468
    ## Objective at step 7 = 750.469
    ## Objective at step 8 = 750.469
    ## Objective at step 9 = 750.469
    ## Objective at step 10 = 750.469
    ## Percentage of flagged cells per variable:
    ##    X1    X2    X3
    ##  0.00 18.67 18.67

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 1

## hbk with response (n=75, d=4)

Also here we find perfect correlation between rd and cd.

    data(hbk)
    X <- as.matrix(hbk)
    X <- transfo(X)$Y

    ##
    ##  The input data has 75 rows and 4 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 15

    cellout <- cellMCD(X)

    ##
    ##  The input data has 75 rows and 4 columns.
    ## Objective at step 0 = 1009.07
    ## Objective at step 0 = 1003.04
    ## Objective at step 1 = 1002.84
    ## Objective at step 2 = 1002.82
    ## Objective at step 3 = 1002.82
    ## Objective at step 4 = 1002.82
    ## Objective at step 5 = 1002.82
    ## Objective at step 6 = 1002.82
    ## Objective at step 7 = 1002.83
    ## Objective at step 8 = 1002.83
    ## Objective at step 9 = 1002.83
    ## Objective at step 10 = 1002.83
    ## Objective at step 11 = 1002.83
    ## Objective at step 12 = 1002.83
    ## Objective at step 13 = 1002.83
    ## Percentage of flagged cells per variable:
    ##    X1    X2    X3     Y
    ##  0.00 18.67 18.67 13.33

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 1

## kootenay (n=13, d=2)

Perfect correlation between rd and cd.

    data(kootenay)
    X <- as.matrix(kootenay)
    X <- transfo(X)$Y

    ##
    ##  The input data has 13 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 1

    cellout <- cellMCD(X)

    ##
    ##  The input data has 13 rows and 2 columns.
    ## Objective at step 0 = 43.0275
    ## Objective at step 0 = 41.4276
    ## Objective at step 1 = 41.5324
    ## Objective at step 2 = 41.5621
    ## Objective at step 3 = 41.5685
    ## Objective at step 4 = 41.57
    ## Objective at step 5 = 41.5704
    ## Objective at step 6 = 41.5705
    ## Percentage of flagged cells per variable:
    ##   Libby Newgate
    ##    7.69    0.00

    cd = sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 1

## lactic (n=13, d=2)

This data set is too discrete. It has only 5 unique values for the first variable. This runs into an error:

    data(lactic)
    X <- as.matrix(lactic)
    X <- transfo(X)$Y

    ##
    ##  The input data has 20 rows and 2 columns.
    ##
    ##  The data contained 1 discrete columns with 5 or fewer values.
    ##  Their column names are:
    ##
    ## [1] X
    ##

    ## Error in checkDataSet(X, fracNA = 1, numDiscrete = checkPars$numDiscrete, :  Only 1 column remains, so we stop.

## milk (n=68, d=8)

Despite substantial numbers of cells flagged in several variables, the correlation between cellMCD and covMCD is over large.

    data(milk)
    X <- as.matrix(milk)
    X <- transfo(X)$Y

    ##
    ##  The input data has 86 rows and 8 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cellout <- cellMCD(X)

    ##
    ##  The input data has 86 rows and 8 columns.
    ## Objective at step 0 = 1983.75
    ## Objective at step 0 = 1289.79
    ## Objective at step 1 = 1221.8
    ## Objective at step 2 = 1216.12
    ## Objective at step 3 = 1218.73
    ## Objective at step 4 = 1219.58
    ## Objective at step 5 = 1219.95
    ## Objective at step 6 = 1220.12
    ## Objective at step 7 = 1220.21
    ## Objective at step 8 = 1220.26
    ## Objective at step 9 = 1220.29
    ## Objective at step 10 = 1220.3
    ## Objective at step 11 = 1220.31
    ## Objective at step 12 = 1220.31
    ## Objective at step 13 = 1220.32
    ## Objective at step 14 = 1220.32
    ## Percentage of flagged cells per variable:
    ##    X1    X2    X3    X4    X5    X6    X7    X8
    ## 11.63  6.98 10.47  2.33 17.44 10.47 10.47  4.65

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.995

## pension (n=13, d=2)

This data set has a skewed second variable.

    data(pension)
    X <- as.matrix(pension)
    X <- transfo(X)$Y

    ##
    ##  The input data has 18 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X,center=rowout$center,cov=rowout$cov))
    cellout <- cellMCD(X)

    ##
    ##  The input data has 18 rows and 2 columns.
    ## Objective at step 0 = 61.4568
    ## Objective at step 0 = 55.77
    ## Objective at step 1 = 55.759
    ## Objective at step 2 = 55.7589
    ## Objective at step 3 = 55.7589
    ## Percentage of flagged cells per variable:
    ##   Income Reserves
    ##    11.11     0.00

    cd <- sqrt(mahalanobis(X,center=cellout$mu,cov=cellout$S))
    round(cor(rd,cd),3)

    ## [1] 0.994

## phosphor (n=18, d=3)

Another data set with very similar rd and cd.

    data(phosphor)
    X <- as.matrix(phosphor)
    X <- transfo(X)$Y

    ##
    ##  The input data has 18 rows and 3 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cellout <- cellMCD(X)

    ##
    ##  The input data has 18 rows and 3 columns.
    ## Objective at step 0 = 127.045
    ## Objective at step 0 = 119.958
    ## Objective at step 1 = 119.928
    ## Objective at step 2 = 119.919
    ## Objective at step 3 = 119.915
    ## Objective at step 4 = 119.913
    ## Objective at step 5 = 119.912
    ## Objective at step 6 = 119.911
    ## Objective at step 7 = 119.911
    ## Percentage of flagged cells per variable:
    ##   inorg organic   plant
    ##    5.56    0.00   11.11

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.947

## pilot (n=20, d=2)

This dataset has a very strong correlation between the variables, but no outliers.

    data(pilot)
    X <- as.matrix(pilot)
    X <- transfo(X)$Y

    ##
    ##  The input data has 20 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 0

    cellout <- cellMCD(X)

    ##
    ##  The input data has 20 rows and 2 columns.
    ## Objective at step 0 = -3.0562
    ## Objective at step 0 = -4.3477
    ## Objective at step 1 = -4.3477
    ## Percentage of flagged cells per variable:
    ## X Y
    ## 0 0

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.893

Note that when no cells are flagged, the raw output of cellMCD is the maximum likelihood estimate, that is, the arithmetic mean and (n-1)/n times the classical covariance matrix:

    cellout$raw.mu - colMeans(X)

    ## numeric(0)

    n <- nrow(X)
    cellout$raw.S - (n - 1) * cov(X) / n

    ##              X            Y
    ## X 1.110223e-16 2.220446e-16
    ## Y 2.220446e-16 1.110223e-16

## radarImage n=1573, d=5

In this example, the data is not elliptical, so caution is warranted.

    data(radarImage)
    X <- as.matrix(radarImage)
    X <- transfo(X)$Y

    ##
    ##  The input data has 1573 rows and 5 columns.

    pairs(X)

plot of chunk unnamed-chunk-37

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975,ncol(X)))
    sum(rd > cutoff)

    ## [1] 88

    cellout <- cellMCD(X)

    ##
    ##  The input data has 1573 rows and 5 columns.
    ## Objective at step 0 = 22208.1
    ## Objective at step 0 = 22035.8
    ## Objective at step 1 = 22022
    ## Objective at step 2 = 22020.9
    ## Objective at step 3 = 22020.9
    ## Objective at step 4 = 22020.9
    ## Percentage of flagged cells per variable:
    ## X.coord Y.coord  Band.1  Band.2  Band.3
    ##    0.13    0.00    3.56    3.56    2.61

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd,cd),3)

    ## [1] 1

## Salinity without response n=28, d=3

There are some differences between cellMCD and covMCD on this dataset. Visually, there do not seem to be very extreme outliers. Nevertheless, covMCD discards three observations. cellMCD on the other hand only discards a single cell.

    data(salinity)
    X <- as.matrix(salinity)[, 1:3]
    X <- transfo(X)$Y

    ##
    ##  The input data has 28 rows and 3 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 5

    cellout <- cellMCD(X)

    ##
    ##  The input data has 28 rows and 3 columns.
    ## Objective at step 0 = 213.685
    ## Objective at step 0 = 211.073
    ## Objective at step 1 = 211.075
    ## Objective at step 2 = 211.076
    ## Objective at step 3 = 211.076
    ## Percentage of flagged cells per variable:
    ##   X1   X2   X3
    ## 0.00 3.57 0.00

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd,cd),3)

    ## [1] 0.885

## Salinity with response (n=28, d=4)

Now the results between covMCD and cellMCD are a little closer. CellMCD flags two cells, and covMCD flags three rows.

    data(salinity)
    X <- as.matrix(salinity)
    X <- transfo(X)$Y

    ##
    ##  The input data has 28 rows and 4 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 6

    cellout <- cellMCD(X)

    ##
    ##  The input data has 28 rows and 4 columns.
    ## Objective at step 0 = 237.36
    ## Objective at step 0 = 224.986
    ## Objective at step 1 = 222.286
    ## Objective at step 2 = 216.157
    ## Objective at step 3 = 216.157
    ## Objective at step 4 = 216.157
    ## Percentage of flagged cells per variable:
    ##   X1   X2   X3    Y
    ## 3.57 0.00 3.57 0.00

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd, cd), 3)

    ## [1] 0.885

## starsCYG (n=47, d=2)

On this data the results of covMCD and cellMCD are very close.

    data(starsCYG)
    X <- as.matrix(starsCYG)
    X <- transfo(X, nbsteps = 1)$Y

    ##
    ##  The input data has 47 rows and 2 columns.

    rowout <- robustbase::covMcd(X)
    rd <- sqrt(mahalanobis(X, center = rowout$center, cov = rowout$cov))
    cutoff <- sqrt(qchisq(0.975, ncol(X)))
    sum(rd > cutoff)

    ## [1] 6

    cellout <- cellMCD(X)

    ##
    ##  The input data has 47 rows and 2 columns.
    ## Objective at step 0 = 252.113
    ## Objective at step 0 = 244.042
    ## Objective at step 1 = 243.788
    ## Objective at step 2 = 243.895
    ## Objective at step 3 = 243.957
    ## Objective at step 4 = 243.982
    ## Objective at step 5 = 243.991
    ## Objective at step 6 = 243.995
    ## Objective at step 7 = 243.997
    ## Objective at step 8 = 243.997
    ## Objective at step 9 = 243.997
    ## Percentage of flagged cells per variable:
    ##    log.Te log.light
    ##     12.77      0.00

    cd <- sqrt(mahalanobis(X, center = cellout$mu, cov = cellout$S))
    round(cor(rd,cd),3)

    ## [1] 0.998

## toxicity (n=38, d=10)

This dataset has too many marginal outliers:

    data(toxicity)
    X <- as.matrix(toxicity)
    X <- transfo(X)$Y

    ##
    ##  The input data has 38 rows and 10 columns.

    rowout <- robustbase::covMcd(X)
    rd     <- sqrt(mahalanobis(X,center=rowout$center,cov=rowout$cov))
    cutoff <- sqrt(qchisq(0.975,ncol(X)))
    sum(rd > cutoff)

    ## [1] 14

    cellout <- cellMCD(X)

    ##
    ##  The input data has 38 rows and 10 columns.

    ## Warning in cellMCD(X): There are fewer than 5 cases per dimension in this data set.
    ## It is not recommended to run cellMCD on these data.
    ## Consider reducing the number of variables.

    ##
    ## At least one variable of X has more than 100*(1-alpha)% = 25%
    ## of marginal outliers.
    ## The percentages per variable are:
    ## toxicity   logKow      pKa    ELUMO    Ecarb     Emet       RM       IR
    ##     2.63     0.00     0.00     0.00     2.63     0.00     0.00     0.00
    ##       Ts        P
    ##     0.00    44.74

    ## Error in cellMCD(X): Too many marginal outliers.

## wood without response n=20, d=5

This data does not satisfy n/d < 5. cellMCD throws a warning in this case, and caution is warranted.

    data(wood)
    X <- as.matrix(wood)
    X <- transfo(X, nbsteps = 1)$Y

    ##
    ##  The input data has 20 rows and 6 columns.

    cellout <- cellMCD(X)

    ##
    ##  The input data has 20 rows and 6 columns.

    ## Warning in cellMCD(X): There are fewer than 5 cases per dimension in this data set.
    ## It is not recommended to run cellMCD on these data.
    ## Consider reducing the number of variables.

    ## Objective at step 0 = 606.678
    ## Objective at step 0 = 357.788
    ## Objective at step 1 = 352.123
    ## Objective at step 2 = 390.227
    ## Objective at step 3 = 431.84
    ## Objective at step 4 = 471.803
    ## Objective at step 5 = 511.841
    ## Objective at step 6 = 552.968
    ## Objective at step 7 = 595.007
    ## Objective at step 8 = 637.136
    ## Objective at step 9 = 678.306
    ## Objective at step 10 = 717.495
    ## Objective at step 11 = 753.873
    ## Objective at step 12 = 786.88
    ## Objective at step 13 = 816.237
    ## Objective at step 14 = 841.913
    ## Objective at step 15 = 864.062
    ## Objective at step 16 = 882.963
    ## Objective at step 17 = 898.957
    ## Objective at step 18 = 912.411
    ## Objective at step 19 = 923.679
    ## Objective at step 20 = 933.09
    ## Objective at step 21 = 940.939
    ## Objective at step 22 = 947.48
    ## Objective at step 23 = 952.932
    ## Objective at step 24 = 957.478
    ## Objective at step 25 = 961.271
    ## Objective at step 26 = 964.441
    ## Objective at step 27 = 967.091
    ## Objective at step 28 = 969.31
    ## Objective at step 29 = 971.17
    ## Objective at step 30 = 972.732
    ## Objective at step 31 = 974.044
    ## Objective at step 32 = 975.147
    ## Objective at step 33 = 976.077
    ## Objective at step 34 = 976.86
    ## Objective at step 35 = 977.52
    ## Objective at step 36 = 978.078
    ## Objective at step 37 = 978.549
    ## Objective at step 38 = 978.947
    ## Objective at step 39 = 979.284
    ## Objective at step 40 = 979.569
    ## Objective at step 41 = 979.811
    ## Objective at step 42 = 980.015
    ## Objective at step 43 = 980.189
    ## Objective at step 44 = 980.336
    ## Objective at step 45 = 980.461
    ## Objective at step 46 = 980.567
    ## Objective at step 47 = 980.657
    ## Objective at step 48 = 980.734
    ## Objective at step 49 = 980.799
    ## Objective at step 50 = 980.855
    ## Objective at step 51 = 980.902
    ## Objective at step 52 = 980.942
    ## Objective at step 53 = 980.976
    ## Objective at step 54 = 981.005
    ## Percentage of flagged cells per variable:
    ## x1 x2 x3 x4 x5  y
    ##  5 15 20 15 10  0