glmnet.measures()
#> $gaussian
#> [1] "mse" "mae"
#>
#> $binomial
#> [1] "deviance" "class" "auc" "mse" "mae"
#>
#> $poisson
#> [1] "deviance" "mse" "mae"
#>
#> $cox
#> [1] "deviance" "C"
#>
#> $multinomial
#> [1] "deviance" "class" "mse" "mae"
#>
#> $mgaussian
#> [1] "mse" "mae"
#>
#> $GLM
#> [1] "deviance" "mse" "mae"15 Assessing models
After fitting regularized regression models with glmnet, we often want to assess their performance on a set of evaluation or test data. After deciding on the performance measure, for each model in the fitted sequence (indexed by the value of \(\lambda\) and possibly \(\gamma\) for relaxed fits, see Chapter 20) we have to build a matrix of predictions and compute the performance measure for it.
15.1 Assessing models with cv.glmnet
cv.glmnet does some of this evaluation automatically. In performing cross-validation (CV), cv.glmnet computes the pre-validated fits (Tibshirani and Efron 2002), that is the model’s predictions of the linear predictor on the held-out folds, and then computes the performance measure with these pre-validated fits. The performance measure is recorded in the cvm element of the returned cv.glmnet and are used to make the CV plot when the plot method is called. When used in this way, one has to pick one of the available performance measures.
glmnet supports a variety of performance measures depending on the model family. The full list of measures can be seen via the call glmnet.measures():
We demonstrate some of this functionality using the spam dataset from the UCI Machine Learning Repository (Mark Hopkins 1999). The spam dataset consists of 4601 emails, classified as either spam or not. The dataset provides 57 input features for each email, which we want to use to predict whether the email is spam.
data_url <- paste0("data/spambase.csv")
df <- read.csv(data_url, header = TRUE)
x <- as.matrix(df[, 1:57])
y <- df[, 58]The user can change the performance measure computed in CV by specifying the type.measure argument. For example, the code below computes the area under the curve (AUC) of the pre-validated fits instead of the deviance which is the default for family = "binomial":
set.seed(1)
cvfit <- cv.glmnet(x, y, family = "binomial", type.measure = "auc")
print(cvfit)
#>
#> Call: cv.glmnet(x = x, y = y, type.measure = "auc", family = "binomial")
#>
#> Measure: AUC
#>
#> Lambda Index Measure SE Nonzero
#> min 0.0000475 90 0.9726 0.002275 56
#> 1se 0.0028463 46 0.9704 0.002531 52
plot(cvfit)
15.2 assess.glmnet
More generally, model assessment can be performed using the assess.glmnet function. The user can pass a class glmnet object, a class cv.glmnet object, or a matrix of predictions to assess.glmnet along with the true response values. The code below shows how one can use assess.glmnet with a class glmnet object. We randomly assign 2/3 of the data as a training set, and the remaining 1/3 as an evaluation set. We train the model on the model matrix x[itrain, ] and response y[itrain], and evaluate the model’s performance on x[-itrain, ] and y[-itrain].
set.seed(1)
itrain <- sample(nrow(x), size = trunc(2 * nrow(x) / 3))
fit <- glmnet(x[itrain, ], y[itrain], family = "binomial")
err <- assess.glmnet(fit, newx = x[-itrain, ], newy = y[-itrain])
names(err)
#> [1] "deviance" "class" "auc" "mse" "mae"
err$deviance
#> s0 s1 s2 s3 s4 s5 s6
#> 1.3264380 1.3015758 1.2763011 1.2320942 1.1872731 1.1467561 1.1092129
#> s7 s8 s9 s10 s11 s12 s13
#> 1.0656452 1.0234352 0.9808725 0.9428621 0.9073433 0.8745313 0.8442765
#> s14 s15 s16 s17 s18 s19 s20
#> 0.8161091 0.7903347 0.7663040 0.7417549 0.7182623 0.6967145 0.6769319
#> s21 s22 s23 s24 s25 s26 s27
#> 0.6587101 0.6415995 0.6257316 0.6109867 0.5970791 0.5843672 0.5727012
#> s28 s29 s30 s31 s32 s33 s34
#> 0.5612451 0.5503952 0.5402986 0.5308190 0.5218406 0.5134895 0.5056022
#> s35 s36 s37 s38 s39 s40 s41
#> 0.4982016 0.4913315 0.4849145 0.4789188 0.4733982 0.4683461 0.4638984
#> s42 s43 s44 s45 s46 s47 s48
#> 0.4599612 0.4565231 0.4535424 0.4509862 0.4487965 0.4468060 0.4451427
#> s49 s50 s51 s52 s53 s54 s55
....By default assess.glmnet will return all possible performance measures for the model family. If a class glmnet object is passed to assess.glmnet, it returns one performance measure value for each model in the \(\lambda\) sequence. The user can get the performance measure values at other values of the \(\lambda\) (and \(\gamma\)) hyperparameters using the s (and gamma) arguments as in the predict method:
assess.glmnet(fit, newx = x[-itrain, ], newy = y[-itrain], s = 0.1)$auc
#> [1] 0.9073383
#> attr(,"measure")
#> [1] "AUC"If a class cv.glmnet object is passed to assess.glmnet, it returns the performance measure value at the lambda.1se value of the \(\lambda\) hyperparameter. (Again, the user can get the performance measure values at other values of the hyperparameters using the s and gamma arguments.)
set.seed(1)
cfit <- cv.glmnet(x[itrain, ], y[itrain], family = "binomial")
cerr <- assess.glmnet(cfit, newx = x[-itrain, ], newy = y[-itrain])
cerr$auc
#> [1] 0.9730154
#> attr(,"measure")
#> [1] "AUC"Remember that by default, assess.glmnet returns performance measures at the lambda.1se hyperparameter value, not the lambda.min value unlike many other packages. If you want to compute the performance measure at the \(\lambda\) value with the best (estimated) CV error, remember to pass the s = "lambda.min" option to assess.glmnet.
Finally, the code below shows how to use assess.glmnet with a matrix of predictions. Note that if a matrix of predictions is passed, the user has to specify the model family via the family argument since assess.glmnet cannot infer that from the inputs. (If no family argument is passed, the default value is family = "gaussian", which might not be what you want!)
pred <- predict(fit, newx = x[-itrain, ])
err <- assess.glmnet(pred, newy = y[-itrain], family = "binomial")
err$auc
#> [1] 0.5000000 0.7801188 0.8125067 0.8492188 0.8762089 0.8830693 0.8955343
#> [8] 0.9111067 0.9199918 0.9291626 0.9346629 0.9398957 0.9435771 0.9464769
#> [15] 0.9489758 0.9510795 0.9531740 0.9559505 0.9585311 0.9605984 0.9621653
#> [22] 0.9635018 0.9646661 0.9657632 0.9665576 0.9670762 0.9675659 0.9681081
#> [29] 0.9687102 0.9692615 0.9697221 0.9702371 0.9706488 0.9709498 0.9711275
#> [36] 0.9713343 0.9716099 0.9718765 0.9720452 0.9723353 0.9725475 0.9726817
#> [43] 0.9728413 0.9729682 0.9730154 0.9730426 0.9731060 0.9731604 0.9731459
#> [50] 0.9730752 0.9730553 0.9730933 0.9730589 0.9730081 0.9730154 0.9730244
#> [57] 0.9729954 0.9729301 0.9728612 0.9728050 0.9726581 0.9725765 0.9725421
#> [64] 0.9725167 0.9724731 0.9724532 0.9724351 0.9723879 0.9723426 0.9723208
#> [71] 0.9722428 0.9722628 0.9722519 0.9722591 0.9722610 0.9722120 0.9721957
#> [78] 0.9721340 0.9720959 0.9721195 0.9721086 0.9721014 0.9720996 0.9721159
#> [85] 0.9721068 0.9721014 0.9720814 0.9720832 0.9720669 0.9720615
#> attr(,"measure")
#> [1] "AUC"One major use of assess.glmnet is to avoid running CV multiple times to get the values for different performance measures. By default, cv.glmnet will only return a single performance measure. However, if the user specifies keep = TRUE in the cv.glmnet call, the pre-validated fits are returned as well. This is a matrix with n rows and nlambda columns. The predictions for each left out fold are stored in their original rows, hence making up a full matrix of predictions. The user can then pass the pre-validated matrix to assess.glmnet. The code below is an example of how to do this. (The keep argument is FALSE by default as the pre-validated matrix is large when the number of training observations is large, thus inflating the size of the returned object.)
set.seed(1)
cfit <- cv.glmnet(x, y, family = "binomial", keep = TRUE)
err <- assess.glmnet(cfit$fit.preval, newy = y, family = "binomial")
err$mae
#> s0 s1 s2 s3 s4 s5 s6
#> 0.9548564 0.9431652 0.9274209 0.9027383 0.8781401 0.8545973 0.8306618
#> s7 s8 s9 s10 s11 s12 s13
#> 0.8050147 0.7783654 0.7516181 0.7257526 0.7006230 0.6763339 0.6531797
#> s14 s15 s16 s17 s18 s19 s20
#> 0.6312674 0.6104358 0.5904550 0.5708270 0.5519254 0.5340363 0.5171700
#> s21 s22 s23 s24 s25 s26 s27
#> 0.5013071 0.4863157 0.4722047 0.4589927 0.4465873 0.4348029 0.4235797
#> s28 s29 s30 s31 s32 s33 s34
#> 0.4130144 0.4030502 0.3936475 0.3847585 0.3763592 0.3683877 0.3607986
#> s35 s36 s37 s38 s39 s40 s41
#> 0.3535694 0.3466947 0.3401176 0.3338284 0.3278858 0.3222445 0.3168672
#> s42 s43 s44 s45 s46 s47 s48
#> 0.3117207 0.3068857 0.3023305 0.2980508 0.2940392 0.2902694 0.2867712
#> s49 s50 s51 s52 s53 s54 s55
....
names(err)
#> [1] "deviance" "class" "auc" "mse" "mae"15.3 roc.glmnet
The roc.glmnet function is specific to binomial data (Chapter 7). roc.glmnet produces the receiver operating characteristic (ROC) curve for the test data. Here is an example of how to do so:
set.seed(1)
cfit <- cv.glmnet(x[itrain, ], y[itrain], family = "binomial")
roc_val <- roc.glmnet(cfit, newx = x[-itrain, ], newy = y[-itrain],
s = "lambda.min")
plot(roc_val, main = "ROC Curve on Test Data", col="orange")
15.4 confusion.glmnet
The confusion.glmnet function is specific to binomial and multinomial data (Chapter 7 and Chapter 8) and produces the confusion matrix for test data. Here is an example of how to use it.
data("MultinomialExample", package = "glmnet")
x <- MultinomialExample$x
y <- MultinomialExample$y
set.seed(101)
itrain <- sample(1:500, 400, replace = FALSE)
cfit <- cv.glmnet(x[itrain, ], y[itrain], family = "multinomial")
cnf <- confusion.glmnet(cfit, newx = x[-itrain, ], newy = y[-itrain])
print(cnf)
#> True
#> Predicted 1 2 3 Total
#> 1 13 6 4 23
#> 2 7 25 5 37
#> 3 4 3 33 40
#> Total 24 34 42 100
#>
#> Percent Correct: 0.71