Data624Homework4

Author

Jonnathan Zuna

3.1

pkgs <- c("mlbench", "caret", "e1071")
new <- pkgs[!(pkgs %in% installed.packages()[, "Package"])]
if (length(new)) install.packages(new)
# Loading required packages
library(mlbench)
library(caret)
library(e1071)

data(Glass)
str(Glass)
'data.frame':   214 obs. of  10 variables:
 $ RI  : num  1.52 1.52 1.52 1.52 1.52 ...
 $ Na  : num  13.6 13.9 13.5 13.2 13.3 ...
 $ Mg  : num  4.49 3.6 3.55 3.69 3.62 3.61 3.6 3.61 3.58 3.6 ...
 $ Al  : num  1.1 1.36 1.54 1.29 1.24 1.62 1.14 1.05 1.37 1.36 ...
 $ Si  : num  71.8 72.7 73 72.6 73.1 ...
 $ K   : num  0.06 0.48 0.39 0.57 0.55 0.64 0.58 0.57 0.56 0.57 ...
 $ Ca  : num  8.75 7.83 7.78 8.22 8.07 8.07 8.17 8.24 8.3 8.4 ...
 $ Ba  : num  0 0 0 0 0 0 0 0 0 0 ...
 $ Fe  : num  0 0 0 0 0 0.26 0 0 0 0.11 ...
 $ Type: Factor w/ 6 levels "1","2","3","5",..: 1 1 1 1 1 1 1 1 1 1 ...

a

par(mfrow = c(3, 3), mar = c(3, 3, 2, 1))
for (v in names(Glass)[1:9]) {
  hist(Glass[[v]], main = v, xlab = "", col = "blue")
}

par(mfrow = c(1, 1))

The histograms of the 9 predictors show that NA and Si are roughly symetric, while Ri, Ai, and Ca are kind of skewed. Mg is bimodal the other groups of glasses are at 0. The spread of the x axes for K, Ba, and Ca might point at outliers.

pairs(Glass[, 1:9], pch = 20, cex = 0.5)

round(cor(Glass[, 1:9]), 2)
      RI    Na    Mg    Al    Si     K    Ca    Ba    Fe
RI  1.00 -0.19 -0.12 -0.41 -0.54 -0.29  0.81  0.00  0.14
Na -0.19  1.00 -0.27  0.16 -0.07 -0.27 -0.28  0.33 -0.24
Mg -0.12 -0.27  1.00 -0.48 -0.17  0.01 -0.44 -0.49  0.08
Al -0.41  0.16 -0.48  1.00 -0.01  0.33 -0.26  0.48 -0.07
Si -0.54 -0.07 -0.17 -0.01  1.00 -0.19 -0.21 -0.10 -0.09
K  -0.29 -0.27  0.01  0.33 -0.19  1.00 -0.32 -0.04 -0.01
Ca  0.81 -0.28 -0.44 -0.26 -0.21 -0.32  1.00 -0.11  0.12
Ba  0.00  0.33 -0.49  0.48 -0.10 -0.04 -0.11  1.00 -0.06
Fe  0.14 -0.24  0.08 -0.07 -0.09 -0.01  0.12 -0.06  1.00

The scatterplot shows that RI and Ca have strong positive linear relationship. Ri has a weaker negative relationship with Si. The other parts shoows no clear pattern of collinearity overall.

b

par(mfrow = c(3, 3), mar = c(3, 3, 2, 1))
for (v in names(Glass)[1:9]) {
  boxplot(Glass[[v]], main = v, horizontal = TRUE)
}

par(mfrow = c(1, 1))

This boxplot show potential outliers in all predictors except Mg

c

sk <- apply(Glass[, 1:9], 2, skewness)
round(sk, 2)
   RI    Na    Mg    Al    Si     K    Ca    Ba    Fe 
 1.60  0.45 -1.14  0.89 -0.72  6.46  2.02  3.37  1.73 

These results agree with the histograms and the outliers seen in the boxplots

d

colSums(Glass[, 1:9] == 0)
 RI  Na  Mg  Al  Si   K  Ca  Ba  Fe 
  0   0  42   0   0  30   0 176 144 

Because of the zeros on 4, a box box transformation cannot be applied to them. but for those that are positive, those are candidates for Box Cox

pos <- c("RI", "Na", "Al", "Si", "Ca")
lambdas <- sapply(pos, function(v) BoxCoxTrans(Glass[[v]])$lambda)
round(lambdas, 2)
  RI   Na   Al   Si   Ca 
-2.0 -0.1  0.5  2.0 -1.1 
bc <- sapply(pos, function(v) {
  predict(BoxCoxTrans(Glass[[v]]), Glass[[v]])
})
round(rbind(before = sk[pos], after = apply(bc, 2, skewness)), 2)
         RI   Na   Al    Si    Ca
before 1.60 0.45 0.89 -0.72  2.02
after  1.57 0.03 0.09 -0.65 -0.19

Applying Box Cox reduced skewness substantially for Na (0.45 to 0.03), Al (0.89 to 0.09), and Ca (2.02 to negative 0.19). RI (1.60 to 1.57) and Si (negative 0.72 to negative 0.65) were essentially unchanged, because their estimated lambdas hit the limits of the search grid and their skew is driven by a few extreme observations. For these, a spatial sign transformation, which handles outliers, may be more appropriate.

ss <- spatialSign(scale(Glass[, 1:9]))
head(round(ss, 3), 3)
      RI    Na    Mg     Al     Si      K     Ca     Ba     Fe
1  0.386 0.126 0.555 -0.306 -0.499 -0.297 -0.064 -0.156 -0.259
2 -0.178 0.423 0.455 -0.122  0.073 -0.019 -0.568 -0.252 -0.419
3 -0.474 0.099 0.395  0.125  0.288 -0.108 -0.545 -0.232 -0.385

A spatial sign transformation was applied after centering and scaling all the 9 predictors, which projects every observation onto a unit sphere. This limits the influence of extreme points, since an observation with large values is divided by its own large length. Unlike Box Cox, it can be applied to Mg, K, Ba, and Fe despite their zeros, and it is a reasonable choice for RI and Si, where Box Cox barely reduced skewness.

range(sqrt(rowSums(ss^2)))
[1] 1 1

3.2

data(Soybean)
dim(Soybean)
[1] 683  36
str(Soybean[, 1:6])
'data.frame':   683 obs. of  6 variables:
 $ Class      : Factor w/ 19 levels "2-4-d-injury",..: 11 11 11 11 11 11 11 11 11 11 ...
 $ date       : Factor w/ 7 levels "0","1","2","3",..: 7 5 4 4 7 6 6 5 7 5 ...
 $ plant.stand: Ord.factor w/ 2 levels "0"<"1": 1 1 1 1 1 1 1 1 1 1 ...
 $ precip     : Ord.factor w/ 3 levels "0"<"1"<"2": 3 3 3 3 3 3 3 3 3 3 ...
 $ temp       : Ord.factor w/ 3 levels "0"<"1"<"2": 2 2 2 2 2 2 2 2 2 2 ...
 $ hail       : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 2 1 1 ...

a

tabs <- lapply(Soybean[, -1], table)
tabs[1:3]
$date

  0   1   2   3   4   5   6 
 26  75  93 118 131 149  90 

$plant.stand

  0   1 
354 293 

$precip

  0   1   2 
 74 112 459 

Frequency tables for the first three predictors show no degenerate variables. date is spread across seven levels, plant.stand is nearly balanced and precip is skewed.

top <- sapply(Soybean[, -1], function(x) max(table(x)) / sum(!is.na(x)))
round(sort(top, decreasing = TRUE), 2)
       mycelium       sclerotia      shriveling       leaf.mild         lodging 
           0.99            0.97            0.93            0.93            0.93 
      leaf.malf    int.discolor       seed.size   seed.discolor          leaves 
           0.92            0.90            0.90            0.89            0.89 
    mold.growth           roots     leaf.shread fruiting.bodies            seed 
           0.89            0.85            0.84            0.82            0.81 
           hail       ext.decay          precip      fruit.pods    plant.growth 
           0.77            0.77            0.71            0.68            0.66 
    fruit.spots       leaf.marg    stem.cankers           sever            temp 
           0.60            0.60            0.59            0.57            0.57 
      leaf.halo            stem     plant.stand       leaf.size        seed.tmt 
           0.57            0.56            0.55            0.55            0.54 
  canker.lesion            germ        area.dam       crop.hist            date 
           0.50            0.37            0.33            0.33            0.22 
nzv <- nearZeroVar(Soybean[, -1], saveMetrics = TRUE)
nzv[nzv$nzv, ]
          freqRatio percentUnique zeroVar  nzv
leaf.mild     26.75     0.4392387   FALSE TRUE
mycelium     106.50     0.2928258   FALSE TRUE
sclerotia     31.25     0.2928258   FALSE TRUE

Using carets nearZeroVar with the standard cutoffs the three Soybean predictors are degenerate. Each is dominated by a single level, so it provides almost no information for separating the 19 classes and is a candidate for removal before modeling.

na_count <- colSums(is.na(Soybean[, -1]))
sort(na_count, decreasing = TRUE)
           hail           sever        seed.tmt         lodging            germ 
            121             121             121             121             112 
      leaf.mild fruiting.bodies     fruit.spots   seed.discolor      shriveling 
            108             106             106             106             106 
    leaf.shread            seed     mold.growth       seed.size       leaf.halo 
            100              92              92              92              84 
      leaf.marg       leaf.size       leaf.malf      fruit.pods          precip 
             84              84              84              84              38 
   stem.cankers   canker.lesion       ext.decay        mycelium    int.discolor 
             38              38              38              38              38 
      sclerotia     plant.stand           roots            temp       crop.hist 
             38              36              31              30              16 
   plant.growth            stem            date        area.dam          leaves 
             16              16               1               1               0 

Missing values are widespread in the Soybean predictors, many predictors share exactly the same number of missing values which suggests that groups of predictors tend to be missing together on the same observations rather than at random. Only date, area.dam, and leaves are nearly complete.

any_na <- rowSums(is.na(Soybean[, -1])) > 0
miss_by_class <- tapply(any_na, Soybean$Class, mean)
round(sort(miss_by_class, decreasing = TRUE), 2)
               2-4-d-injury               cyst-nematode 
                       1.00                        1.00 
diaporthe-pod-&-stem-blight            herbicide-injury 
                       1.00                        1.00 
           phytophthora-rot         alternarialeaf-spot 
                       0.77                        0.00 
                anthracnose            bacterial-blight 
                       0.00                        0.00 
          bacterial-pustule                  brown-spot 
                       0.00                        0.00 
             brown-stem-rot                charcoal-rot 
                       0.00                        0.00 
      diaporthe-stem-canker                downy-mildew 
                       0.00                        0.00 
         frog-eye-leaf-spot      phyllosticta-leaf-spot 
                       0.00                        0.00 
             powdery-mildew           purple-seed-stain 
                       0.00                        0.00 
       rhizoctonia-root-rot 
                       0.00 
table(Soybean$Class[any_na])

               2-4-d-injury         alternarialeaf-spot 
                         16                           0 
                anthracnose            bacterial-blight 
                          0                           0 
          bacterial-pustule                  brown-spot 
                          0                           0 
             brown-stem-rot                charcoal-rot 
                          0                           0 
              cyst-nematode diaporthe-pod-&-stem-blight 
                         14                          15 
      diaporthe-stem-canker                downy-mildew 
                          0                           0 
         frog-eye-leaf-spot            herbicide-injury 
                          0                           8 
     phyllosticta-leaf-spot            phytophthora-rot 
                          0                          68 
             powdery-mildew           purple-seed-stain 
                          0                           0 
       rhizoctonia-root-rot 
                          0 
sum(any_na)
[1] 121

The first four classes are missing values in every observation, so deleting incomplete rows would remove them from the data entirely and leave phytophthora-rot with only about 20 observations.

cc <- Soybean[complete.cases(Soybean), ]
cbind(all = table(Soybean$Class), complete = table(cc$Class))
                            all complete
2-4-d-injury                 16        0
alternarialeaf-spot          91       91
anthracnose                  44       44
bacterial-blight             20       20
bacterial-pustule            20       20
brown-spot                   92       92
brown-stem-rot               44       44
charcoal-rot                 20       20
cyst-nematode                14        0
diaporthe-pod-&-stem-blight  15        0
diaporthe-stem-canker        20       20
downy-mildew                 20       20
frog-eye-leaf-spot           91       91
herbicide-injury              8        0
phyllosticta-leaf-spot       20       20
phytophthora-rot             88       20
powdery-mildew               20       20
purple-seed-stain            20       20
rhizoctonia-root-rot         20       20

A model trained on the complete cases could never predict these diseases, row deletion is not an acceptable strategy for this dataset.

drop_cols <- c("mycelium", "sclerotia", "leaf.mild")
soy <- Soybean[, !(names(Soybean) %in% drop_cols)]
soy[, -1] <- lapply(soy[, -1], function(x) {
  x <- addNA(x)
  levels(x)[is.na(levels(x))] <- "missing"
  x
})
colSums(is.na(soy))[1:5]
      Class        date plant.stand      precip        temp 
          0           0           0           0           0 

After recoding, no missing values remain in any predictor, so all 683 observations, including the four classes that would otherwise be lost, are kept for modeling

table(soy$hail)

      0       1 missing 
    435     127     121 

In the Soybean data, caret’s nearZeroVar flagged 3 degenerate predictors: mycelium, sclerotia, and leaf.mild. Missing values are strongly tied to the class, since 4 classes have them in every observation and 14 classes have none.

Deleting incomplete rows would remove four classes entirely, so I dropped the 3 degenerate predictors and recoded missing values as their own factor level, which keeps all 683 observations.