На наборе данных из своего варианта построить указанные модели для прогноза бинарной зависимой переменной. Доля обучающей выборки – 75%.
Построить три графика:
1. Матричный график взаимного разброса переменных модели (ggpairs).
1. Две ROC-кривые на одних осях: сравнение качества прогноза сравниваемых моделей на обучающей выборке.
1. Две ROC-кривые на одних осях: сравнение качества прогноза сравниваемых моделей на тестовой выборке.
В конце файла с кодом в комментарии сравнить модели по качеству с помощью ROC-кривых. Сделать предположение о том, что в данном случае повлияло на преимущество одного метода над другим.
Скрипт решения и графики разместить в репозитории на github, прислать ссылку на почту преподавателя.
Номер варианта – номер студента в списке. Студент под номером 19 берёт вариант 1, под номером 20 – 2, и т.д.
| Вариант | Ядро для set.seed() |
Данные | Зависимая переменная | Объясняющие переменные | Методы |
|---|---|---|---|---|---|
| 5 | 234 | Default{ISLR} – долги по кредитным картам | default (Yes – наличие признака, No – отсутствие) |
все остальные | Логистическая регрессия, QDA |
library('ISLR')
library('GGally')
## Loading required package: ggplot2
## Registered S3 method overwritten by 'GGally':
## method from
## +.gg ggplot2
library('MASS')
library('ROCR') # ROC-кривые
Зададим ядро генератора случайных чисел (по номеру варианта) и объём обучающей выборки.
my.seed <- 234
train.percent <- 0.75
options("ggmatrix.progress.bar" = FALSE)
Подключим исходные данные и исследуем их структуру
## 'data.frame': 10000 obs. of 4 variables:
## $ default: Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
## $ student: Factor w/ 2 levels "No","Yes": 1 2 1 1 1 2 1 2 1 1 ...
## $ balance: num 730 817 1074 529 786 ...
## $ income : num 44362 12106 31767 35704 38463 ...
## default student balance income
## 1 No No 729.5265 44361.625
## 2 No Yes 817.1804 12106.135
## 3 No No 1073.5492 31767.139
## 4 No No 529.2506 35704.494
## 5 No No 785.6559 38463.496
## 6 No Yes 919.5885 7491.559
Построим матричный график взаимного разброса переменных модели На графиках класс неплательщиков (default = ‘Yes’) показан красным.
# графики разброса
ggp <- ggpairs(Default, aes(colour = default, alpha = 0.4)) +
scale_colour_manual(values = c('darkgreen', 'red')) +
scale_fill_manual(values = c('darkgreen', 'red'))
print(ggp, progress = FALSE)
## Warning in ggmatrix_gtable(x, ...): Please use the 'progress' parameter in
## your ggmatrix-like function call. See ?ggmatrix_progress for a few examples.
## ggmatrix_gtable 'progress' and 'progress_format' will soon be deprecated.TRUE
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Априорные вероятности классов, которые можно оценить как доли наблюдений каждого класса в выборке, неодинаковы. Неплательщиков (default == 1) намного меньше:
# доли наблюдений в столбце default
table(Default$default) / sum(table(Default$default))
##
## No Yes
## 0.9667 0.0333
Доля наименьшего класса, в данном случае 0.0333, это ошибка нулевого классификатора: если бы мы прогнозировали default = Yes для всех наблюдений, ровно в такой доле случаев мы бы ошиблись. Точность моделей целесообразно будет сравнивать с этой величиной. Построим модели, используя в качестве объясняющего фактора все остальные переменные.
set.seed(my.seed)
inTrain <- sample(seq_along(Default$default), nrow(Default)*train.percent)
df_train <- Default[inTrain, ]
df_test <- Default[-inTrain, ]
# фактические значения на обучающей выборке
Факт <- df_train$default
model.logit <- glm(default ~ ., data = df_train, family = 'binomial')
summary(model.logit)
##
## Call:
## glm(formula = default ~ ., family = "binomial", data = df_train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.1867 -0.1277 -0.0489 -0.0173 3.8071
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.159e+01 6.089e-01 -19.035 <2e-16 ***
## studentYes -4.828e-01 2.842e-01 -1.699 0.0894 .
## balance 5.960e-03 2.838e-04 21.006 <2e-16 ***
## income 9.880e-06 9.772e-06 1.011 0.3120
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 2103.9 on 7499 degrees of freedom
## Residual deviance: 1092.0 on 7496 degrees of freedom
## AIC: 1100
##
## Number of Fisher Scoring iterations: 8
Параметр balance модели логистической регрессии значим с вероятностью 0.99.
# прогноз: вероятности принадлежности классу 'Yes' (дефолт)
forecast_logit_train <- predict(model.logit, df_train, type = 'response')
Вектор forecast_logit_train состоит из вероятностей принадлежности наблюдений к классам, а не из меток этих классов. Поэтому для прогноза нужно сделать разделение на два класса вручную, используя какую-то границу отсечения. В данном случае это 0.5 – значение по умолчанию.
Прогноз <- factor(ifelse(forecast_logit_train > 0.5, 2, 1),levels = c(1, 2),labels = c('No', 'Yes'))
# матрица неточностей
conf.m <- table(Факт, Прогноз)
conf.m
## Прогноз
## Факт No Yes
## No 7237 26
## Yes 148 89
У этой модели низкая чувствительность:
# чувствительность
conf.m[2, 2] / sum(conf.m[2, ])
## [1] 0.3755274
# специфичность
conf.m[1, 1] / sum(conf.m[1, ])
## [1] 0.9964202
# верность
sum(diag(conf.m)) / sum(conf.m)
## [1] 0.9768
Построим модель:
model.qda <- qda(default ~ ., data = Default[inTrain, ])
model.qda
## Call:
## qda(default ~ ., data = Default[inTrain, ])
##
## Prior probabilities of groups:
## No Yes
## 0.9684 0.0316
##
## Group means:
## studentYes balance income
## No 0.2899628 802.8205 33627.38
## Yes 0.3628692 1766.1912 32944.40
# прогноз: вероятности принадлежности классу 'Yes' (дефолт)
forecast_qda_train <- predict(model.qda, df_train, type = 'response')
Прогноз <- factor(ifelse(forecast_qda_train$posterior[, 'Yes'] > 0.5, 2, 1),levels = c(1, 2), labels = c('No', 'Yes'))
# матрица неточностей
conf.m <- table(Факт, Прогноз)
conf.m
## Прогноз
## Факт No Yes
## No 7245 18
## Yes 161 76
Разделяющая граница квадратичного дисперсионного анализа нелинейна, поэтому коэффициентов в отчёте мы не видим. Чувствительность немного выше, чем у логистичесокй регрессии.
# чувствительность
conf.m[2, 2] / sum(conf.m[2, ])
## [1] 0.3206751
# специфичность
conf.m[1, 1] / sum(conf.m[1, ])
## [1] 0.9975217
# верность
sum(diag(conf.m)) / sum(conf.m)
## [1] 0.9761333
Такая чувствительность не может нас устраивать, поскольку высокое значение верности модели (accuracy) обусловлено исключительно большой долей одного из классов в выборке. В такой ситуации надо пожертвовать небольшой частью специфичности, чтобы подтянуть чувствительность. Сделаем это, изменив границу отсечения классов.
Построим следующие ROC-кривые:
Две ROC-кривые на одних осях: сравнение качества прогноза сравниваемых моделей на обучающей выборке.
Две ROC-кривые на одних осях: сравнение качества прогноза сравниваемых моделей на тестовой выборке.
# функция построения ROC-кривой: pred -- прогноз, truth -- факт
rocplot <- function(pred, truth, ...){
require(ROCR)
# вызов функции непосредственно из пакета ROCR
predob = ROCR::prediction(pred, truth)
perf = performance(predob, "tpr", "fpr")
plot(perf,...)}
# Прогноз для обучающей выборки
p_logit_train <- predict(model.logit, df_train, type = 'response')
p_qda_train <- predict(model.qda, df_train, type = 'response')
forecast_logit_train <- factor(ifelse(forecast_logit_train > 0.5, 2, 1),levels = c(1, 2), labels = c('No', 'Yes'))
forecast_qda_train <- factor(ifelse(forecast_qda_train$posterior[, 'Yes'] > 0.5, 2, 1),levels = c(1, 2), labels = c('No', 'Yes'))
# Прогноз для тестовой выборки
p_logit_test <- predict(model.logit, df_test, type = 'response')
p_qda_test <- predict(model.qda, df_test, type = 'response')
forecast_logit_test <- factor(ifelse(p_logit_test > 0.5, 2, 1),levels = c(1, 2), labels = c('No', 'Yes'))
forecast_qda_test <- factor(ifelse(p_qda_test$posterior[, 'Yes'] > 0.5, 2, 1),levels = c(1, 2), labels = c('No', 'Yes'))
par(mfrow = c(1, 2))
# график для обучающей выборки
rocplot(p_logit_train, df_train[, "default"], main = "Обучающий набор")
rocplot(p_qda_train[["posterior"]][,1], df_train[, "default"], add = T, col = "red")
# график для тестовой выборки
rocplot(p_logit_test, df_test[, "default"], main = "Тестовый набор")
rocplot(p_qda_test[["posterior"]][,1], df_test[, "default"], add = T, col = "red")