This vignette visualizes classification results from rpart (CART), using tools from the package. The displays in this vignette are discussed in section 4 of Raymaekers and Rousseeuw (2021) (for the training data), and in section A.4 of the Supplementary Material (for the test data).
library(rpart)
library(classmap)
We first load and inspect the data.
data("data_titanic")
traindata <- data_titanic[which(data_titanic$dataType == "train"), -13]
dim(traindata)
## [1] 891 12
colnames(traindata)
## [1] "PassengerId" "Pclass" "Name" "Sex" "Age"
## [6] "SibSp" "Parch" "Ticket" "Fare" "Cabin"
## [11] "Embarked" "y"
# SibSp: number of siblings + spouses aboard
# Parch: number of parents + children aboard
str(traindata)
## 'data.frame': 891 obs. of 12 variables:
## $ PassengerId: int 1 2 3 4 5 6 7 8 9 10 ...
## $ Pclass : int 3 1 3 1 3 3 1 3 3 2 ...
## $ Name : chr "Braund, Mr. Owen Harris" "Cumings, Mrs. John Bradley (Florence Briggs Thayer)" "Heikkinen, Miss. Laina" "Futrelle, Mrs. Jacques Heath (Lily May Peel)" ...
## $ Sex : chr "male" "female" "female" "female" ...
## $ Age : num 22 38 26 35 35 NA 54 2 27 14 ...
## $ SibSp : int 1 1 0 1 0 0 0 3 0 1 ...
## $ Parch : int 0 0 0 0 0 0 0 1 2 0 ...
## $ Ticket : chr "A/5 21171" "PC 17599" "STON/O2. 3101282" "113803" ...
## $ Fare : num 7.25 71.28 7.92 53.1 8.05 ...
## $ Cabin : chr "" "C85" "" "C123" ...
## $ Embarked : chr "S" "C" "S" "S" ...
## $ y : Factor w/ 2 levels "casualty","survived": 1 2 2 2 1 1 1 1 2 2 ...
table(traindata$y)
##
## casualty survived
## 549 342
Now we want to predict the response y by CART (rpart). We set the seed as rpart is not deterministic. We also inspect the resulting classification tree.
set.seed(123)
rpart.out <- rpart(y ~ Pclass + Sex + SibSp +
Parch + Fare + Embarked,
data = traindata, method = 'class',
model = TRUE)
## plot the tree:
# pdf(file = "titanic_train_tree.pdf", width = 6, height = 6)
rpart.plot::rpart.plot(rpart.out, box.palette = "RdBu")
# dev.off()
We now inspect the variable importance, which will be used to calculate the variable weights in the farness computation.
rpart.out$variable.importance
## Sex Fare Pclass Parch Embarked SibSp
## 124.426330 43.978667 31.163132 16.994228 9.889646 8.349703
Now we declare the types of the variables. This is used in the calculation of the Daisy distance, which in turn is needed for the farness computation. There are 5 nominal columns and one ordinal. The variables not listed here are interval-scaled by default.
mytype <- list(nominal = c("Name", "Sex", "Ticket", "Cabin", "Embarked"), ordratio = c("Pclass"))
Now we prepare for visualization and inspect the elements of the output.
x_train <- traindata[, -12]
y_train <- traindata[, 12]
vcrtrain <- vcr.rpart.train(x_train, y_train, rpart.out, mytype)
names(vcrtrain)
## [1] "X" "yint" "y" "levels" "predint" "pred"
## [7] "altint" "altlab" "PAC" "figparams" "fig" "farness"
## [13] "ofarness" "trainfit"
vcrtrain$predint[1:10] # prediction as integer
## [1] 1 2 1 2 1 1 1 1 2 2
vcrtrain$pred[1:10] # prediction as label
## [1] "casualty" "survived" "casualty" "survived" "casualty" "casualty"
## [7] "casualty" "casualty" "survived" "survived"
vcrtrain$altint[1:10] # alternative label as integer
## [1] 2 1 1 1 2 2 2 2 1 1
vcrtrain$altlab[1:10] # alternative label
## [1] "survived" "casualty" "casualty" "casualty" "survived" "survived"
## [7] "survived" "survived" "casualty" "casualty"
# Probability of Alternative Class (PAC) of each object:
vcrtrain$PAC[1:3]
## [1] 0.18890815 0.05294118 0.59459459
#
summary(vcrtrain$PAC)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.05294 0.18891 0.18891 0.27905 0.29630 0.94706
# f(i, g) is the distance from case i to class g:
vcrtrain$fig[1:3, ] # for the first 3 objects:
## [,1] [,2]
## [1,] 0.2621228 0.5079752
## [2,] 0.9505125 0.3728041
## [3,] 0.2303989 0.1644634
# The farness of an object i is the f(i, g) to its own class:
vcrtrain$farness[1:3]
## [1] 0.2621228 0.3728041 0.1644634
#
summary(vcrtrain$farness)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.0000 0.2437 0.3286 0.6234 0.9613
# The "overall farness" of an object is defined as the
# lowest f(i, g) it has to any class g (including its own):
summary(vcrtrain$ofarness)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.0000 0.1919 0.2906 0.5342 0.9578
sum(vcrtrain$ofarness > 0.99, na.rm = TRUE)
## [1] 0
# No farness is considered outlying in these data.
confmat.vcr(vcrtrain)
##
## Confusion matrix:
## predicted
## given casualty survived
## casualty 521 28
## survived 130 212
##
## The accuracy is 82.27%.
cols <- c("firebrick", "blue")
We first make the stacked plot, visualizing the confusion matrix:
stackedplot(vcrtrain, classCols = cols,
main = "Stacked plot of rpart on Titanic training data")
Now we consider the silhouette plot
silplot(vcrtrain, classCols = cols,
main = "Silhouettes of rpart on Titanic training data")
## classNumber classLabel classSize classAveSi
## 1 casualty 549 0.55
## 2 survived 342 0.27
# silplot.out <- silplot(vcrtrain, classCols = cols)
# ggsave("titanic_train_silhouettes.pdf", silplot.out,
# width = 5, height = 5)
Now we construct the quasi residual plot of PAC vs. age for males only. The age variable is not in the model, but including it did not improve the classification. We see that the very young male passengers are often misclassified.
hist(x_train$Age)
# Quasi residual plot versus age, for males only:
# pdf("titanic_qrp_versus_age_males.pdf", width = 4.8, height = 4.8)
PAC <- vcrtrain$PAC[which(x_train$Sex == "male")]
feat <- x_train$Age[which(x_train$Sex == "male")]
qresplot(PAC, feat, xlab = "Age (years)", opacity = 0.5,
main = "quasi residual plot for male passengers",
plotLoess = TRUE)
text(x = 14, y = 0.60, "loess curve", col = "red", cex = 1)
# dev.off()
Now we construct the class maps. First of the casualties.
classmap(vcrtrain, "casualty", classCols = cols)
# classmap(vcrtrain, "casualty", classCols = cols, identify = TRUE)
# blue points top right: cases "a" and "b" in the paper
x_train[which(y_train == "casualty")[119], ]
## PassengerId Pclass Name Sex Age SibSp Parch
## 178 178 1 Isham, Miss. Ann Elizabeth female 50 0 0
## Ticket Fare Cabin Embarked
## 178 PC 17595 28.7125 C49 C
# Woman in 1st class, should have survived.
#
x_train[which(y_train == "casualty")[192], ]
## PassengerId Pclass Name Sex Age SibSp Parch
## 298 298 1 Allison, Miss. Helen Loraine female 2 1 2
## Ticket Fare Cabin Embarked
## 298 113781 151.55 C22 C26 S
# Similar, but child.
# red point most to the right: case "c" in the paper
x_train[which(y_train == "casualty")[268], ]
## PassengerId Pclass Name Sex Age SibSp Parch Ticket Fare
## 439 439 1 Fortune, Mr. Mark male 64 1 4 19950 263
## Cabin Embarked
## 439 C23 C25 C27 S
Now the class map of the survivors.
classmap(vcrtrain, "survived", classCols = cols)
# classmap(vcrtrain, "survived", classCols = cols, identify = TRUE)
# red point with highest farness among highest PAC: case "d" in the paper
x_train[which(y_train == "survived")[c(14)], ]
## PassengerId Pclass Name
## 26 26 3 Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)
## Sex Age SibSp Parch Ticket Fare Cabin Embarked
## 26 female 38 1 5 347077 31.3875 S
# near-coinciding points with highest farness: cases "e" and "f" in the paper
#
x_train[which(y_train == "survived")[265], ]
## PassengerId Pclass Name Sex Age SibSp Parch
## 680 680 1 Cardeza, Mr. Thomas Drake Martinez male 36 0 1
## Ticket Fare Cabin Embarked
## 680 PC 17755 512.3292 B51 B53 B55 C
x_train[which(y_train == "survived")[287], ]
## PassengerId Pclass Name Sex Age SibSp Parch Ticket
## 738 738 1 Lesurer, Mr. Gustave J male 35 0 0 PC 17755
## Fare Cabin Embarked
## 738 512.3292 B101 C
# man --> predicted as not survived. also paid the highest
# fare in the data and has same ticket number as passenger
# 265. Also embarked at the same place. It turns out that
# Gustave Lesueur was the man servant of the banker Thomas Cardeza:
# https://www.encyclopedia-titanica.org/titanic-survivor/thomas-cardeza.html
# https://www.encyclopedia-titanica.org/titanic-survivor/gustave-lesueur.html
# blue point bottom right: case "g" in the paper
x_train[which(y_train == "survived")[90], ]
## PassengerId Pclass Name Sex Age SibSp Parch Ticket
## 259 259 1 Ward, Miss. Anna female 35 0 0 PC 17755
## Fare Cabin Embarked
## 259 512.3292 C
# # woman + first class so predicted in survivors.
# # paid highest fare in whole dataset
In addition to the training data, we consider the titanic test data. First we load the data and inspect.
testdata <- data_titanic[which(data_titanic$dataType == "test"), -13]
dim(testdata)
## [1] 418 12
x_test <- testdata[, -12]
y_test <- testdata[, 12]
table(y_test)
## y_test
## casualty survived
## 260 158
Now we prepare for visualization:
vcrtest <- vcr.rpart.newdata(x_test, y_test, vcrtrain)
confmat.vcr(vcrtest)
##
## Confusion matrix:
## predicted
## given casualty survived
## casualty 230 30
## survived 64 94
##
## The accuracy is 77.51%.
cols <- c("firebrick", "blue")
Now we can visualize, starting with the stacked plot:
stackedplot(vcrtest, classCols = cols,
main = "Stacked plot of Titanic test data")
Now we construct the silhouette plot:
silplot(vcrtest, classCols = cols,
main = "Silhouettes of rpart on Titanic test data")
## classNumber classLabel classSize classAveSi
## 1 casualty 260 0.47
## 2 survived 158 0.25
Finally, we make the class maps. First of the casualties, in which the misclassifications are all female (since all males are predicted as casualty by this model).
classmap(vcrtest, "casualty", classCols = cols)
Now the class map of survivors:
classmap(vcrtest, "survived", classCols = cols)