Данные: Online_Shopping_for_models.csv

library('GGally')       # графики совместного разброса переменных
## Warning: package 'GGally' was built under R version 3.5.3
## Loading required package: ggplot2
library('lmtest') # тесты остатков регрессионных моделей
## Warning: package 'lmtest' was built under R version 3.5.3
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
library('FNN') # алгоритм kNN
## Warning: package 'FNN' was built under R version 3.5.3
library('mlbench')
## Warning: package 'mlbench' was built under R version 3.5.3
library('MASS')
## Warning: package 'MASS' was built under R version 3.5.3
library('ISLR')
## Warning: package 'ISLR' was built under R version 3.5.3
library('boot')
setwd("D:/Desktop")
DF <- read.table('Online_Shopping_for_models.csv', header = T,            # заголовок в первой строке
                 dec = ',',             # разделитель целой и дробной части
                 sep = ';')     # символы пропущенных значений
df <- na.omit(DF)
dim(df)
## [1] 11097    18
head(df)
str(df)
## 'data.frame':    11097 obs. of  18 variables:
##  $ Administrative         : int  1 1 2 11 10 0 2 8 0 0 ...
##  $ Administrative_Duration: num  28.2 6 51 471.6 237.8 ...
##  $ Informational          : int  0 0 0 4 2 0 0 1 0 0 ...
##  $ Informational_Duration : num  0 0 0 236 23 ...
##  $ ProductRelated         : int  1 15 25 22 82 4 16 227 3 1 ...
##  $ ProductRelated_Duration: num  0 762 699 883 1815 ...
##  $ BounceRates            : num  0 0 0 0.00645 0.00233 ...
##  $ ExitRates              : num  0.05 0.01429 0.00873 0.0182 0.01039 ...
##  $ PageValues             : num  0 0 0 19.4 8.5 ...
##  $ SpecialDay             : num  0 0 0 0 0 0.8 0 0 0 0 ...
##  $ Month                  : Factor w/ 10 levels "Aug","Dec","Feb",..: 10 2 7 10 7 7 3 9 7 6 ...
##  $ OperatingSystems       : int  1 8 2 3 3 2 2 2 2 1 ...
##  $ Browser                : int  1 13 10 2 2 2 2 2 2 1 ...
##  $ Region                 : int  1 9 1 3 3 1 1 1 3 1 ...
##  $ TrafficType            : int  5 20 4 4 2 13 6 13 1 9 ...
##  $ VisitorType            : Factor w/ 3 levels "New_Visitor",..: 1 2 3 3 3 3 3 3 3 3 ...
##  $ Weekend                : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ Revenue                : logi  TRUE FALSE FALSE FALSE FALSE FALSE ...
my.seed <- 12  
set.seed(12)
set.seed(my.seed)
summary(df)
##  Administrative   Administrative_Duration Informational    
##  Min.   : 0.000   Min.   :   0.00         Min.   : 0.0000  
##  1st Qu.: 0.000   1st Qu.:   0.00         1st Qu.: 0.0000  
##  Median : 1.000   Median :   8.00         Median : 0.0000  
##  Mean   : 2.321   Mean   :  80.69         Mean   : 0.5047  
##  3rd Qu.: 4.000   3rd Qu.:  92.75         3rd Qu.: 0.0000  
##  Max.   :27.000   Max.   :3398.75         Max.   :24.0000  
##                                                            
##  Informational_Duration ProductRelated  ProductRelated_Duration
##  Min.   :   0.00        Min.   :  0.0   Min.   :    0.0        
##  1st Qu.:   0.00        1st Qu.:  7.0   1st Qu.:  186.5        
##  Median :   0.00        Median : 18.0   Median :  598.6        
##  Mean   :  34.51        Mean   : 31.9   Mean   : 1199.3        
##  3rd Qu.:   0.00        3rd Qu.: 38.0   3rd Qu.: 1474.5        
##  Max.   :2549.38        Max.   :705.0   Max.   :63973.5        
##                                                                
##   BounceRates         ExitRates         PageValues        SpecialDay     
##  Min.   :0.000000   Min.   :0.00000   Min.   :  0.000   Min.   :0.00000  
##  1st Qu.:0.000000   1st Qu.:0.01423   1st Qu.:  0.000   1st Qu.:0.00000  
##  Median :0.003139   Median :0.02513   Median :  0.000   Median :0.00000  
##  Mean   :0.022350   Mean   :0.04313   Mean   :  5.917   Mean   :0.06232  
##  3rd Qu.:0.016949   3rd Qu.:0.05000   3rd Qu.:  0.000   3rd Qu.:0.00000  
##  Max.   :0.200000   Max.   :0.20000   Max.   :361.764   Max.   :1.00000  
##                                                                          
##      Month      OperatingSystems    Browser           Region    
##  May    :3036   Min.   :1.000    Min.   : 1.000   Min.   :1.00  
##  Nov    :2710   1st Qu.:2.000    1st Qu.: 2.000   1st Qu.:1.00  
##  Mar    :1703   Median :2.000    Median : 2.000   Median :3.00  
##  Dec    :1537   Mean   :2.123    Mean   : 2.352   Mean   :3.15  
##  Oct    : 492   3rd Qu.:3.000    3rd Qu.: 2.000   3rd Qu.:4.00  
##  Sep    : 402   Max.   :8.000    Max.   :13.000   Max.   :9.00  
##  (Other):1217                                                   
##   TrafficType                VisitorType    Weekend         Revenue       
##  Min.   : 1.000   New_Visitor      :1537   Mode :logical   Mode :logical  
##  1st Qu.: 2.000   Other            :  76   FALSE:8512      FALSE:9367     
##  Median : 2.000   Returning_Visitor:9484   TRUE :2585      TRUE :1730     
##  Mean   : 4.099                                                           
##  3rd Qu.: 4.000                                                           
##  Max.   :20.000                                                           
## 

Выдвинем предположение о том, что выручка интернет-магазина будет зависить от показателя отказов, руководства, браузера и соответствующей продукции.

model1 <- lm(Revenue ~ BounceRates +  Administrative + Browser + ProductRelated, data = df)
summary(model1)
## 
## Call:
## lm(formula = Revenue ~ BounceRates + Administrative + Browser + 
##     ProductRelated, data = df)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -0.85767 -0.17259 -0.13893 -0.08463  1.03913 
## 
## Coefficients:
##                  Estimate Std. Error t value Pr(>|t|)    
## (Intercept)     1.140e-01  6.836e-03  16.671  < 2e-16 ***
## BounceRates    -8.427e-01  7.155e-02 -11.778  < 2e-16 ***
## Administrative  8.056e-03  1.136e-03   7.091 1.41e-12 ***
## Browser         6.457e-03  1.961e-03   3.294 0.000992 ***
## ProductRelated  8.425e-04  8.316e-05  10.131  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.3545 on 11092 degrees of freedom
## Multiple R-squared:  0.04551,    Adjusted R-squared:  0.04517 
## F-statistic: 132.2 on 4 and 11092 DF,  p-value: < 2.2e-16

Все переменные модели являются значимыми. Построим совместный график разброса переменных.

ggp <- ggpairs(df[, c('Revenue','BounceRates', 'Administrative', 'Browser','ProductRelated')], 
               mapping = ggplot2::aes(color = Revenue))
print(ggp, progress = F)
## 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`.

Перекрёстная проверка по отдельным наблюдениям. Это самый затратный в вычислительном плане метод, но и самый надёжный в плане оценки ошибки вне выборки. Попробуем применить его к линейной модели.

fit.glm <- glm(Revenue ~ BounceRates +  Administrative + Browser + ProductRelated, data = df)
# считаем LOOCV-ошибку
cv.err <- cv.glm(df, fit.glm)
# результат: первое число -- по формуле LOOCV-ошибки,
#  второе -- с поправкой на смещение
cv.err$delta[1]
## [1] 0.1257318

Ошибка модели оказалась очень низкой. Оценим точность полиномиальных моделей, поочередно меняя степень каждого из регрессора, чтобы найти наилучшую модель.

Регрессор 1

cv.err.loocv <- rep(0, 5)
names(cv.err.loocv) <- 1:5
# цикл по степеням полиномов
for (i in 1:5){
  fit.glm <- glm(Revenue ~ poly(BounceRates,i) +  Administrative + Browser + ProductRelated, data = df)
  cv.err.loocv[i] <- cv.glm(df, fit.glm)$delta[1]
}
# результат
cv.err.loocv
##         1         2         3         4         5 
## 0.1257318 0.1247703 0.1245650 0.1245298 0.1245363

Регрессор 2

cv.err.loocv <- rep(0, 5)
names(cv.err.loocv) <- 1:5
# цикл по степеням полиномов
for (i in 1:5){
  fit.glm <- glm(Revenue ~ BounceRates +  poly(Administrative,i) + Browser + ProductRelated, data = df)
  cv.err.loocv[i] <- cv.glm(df, fit.glm)$delta[1]
}
# результат
cv.err.loocv
##         1         2         3         4         5 
## 0.1257318 0.1252479 0.1252630 0.1252620 0.1253488

Регрессор 3

cv.err.loocv <- rep(0, 5)
names(cv.err.loocv) <- 1:5
# цикл по степеням полиномов
for (i in 1:5){
  fit.glm <- glm(Revenue ~ BounceRates +  Administrative + poly(Browser,i) + ProductRelated, data = df)
  cv.err.loocv[i] <- cv.glm(df, fit.glm)$delta[1]
}
# результат
cv.err.loocv
##         1         2         3         4         5 
## 0.1257318 0.1257283 0.1257510 0.1257656 0.1257741

Регрессор 4

cv.err.loocv <- rep(0, 5)
names(cv.err.loocv) <- 1:5
# цикл по степеням полиномов
for (i in 1:5){
  fit.glm <- glm(Revenue ~ BounceRates +  Administrative + Browser + poly(ProductRelated,i), data = df)
  cv.err.loocv[i] <- cv.glm(df, fit.glm)$delta[1]
}
# результат
cv.err.loocv
##         1         2         3         4         5 
## 0.1257318 0.1252452 0.1252405 0.1251493 0.1251646

Ошибка модели при изменении степени каждого из регрессора практически не меняется. За наилучшую примем первоначальную линейную модель.

Построим график совместного изменения чувствительности и специфичности с изменением вероятности отсечения от 0 до 1 – ROC-кривую. Для примера возьмём модель LDA.

Факт <- df$Revenue

model.logit <- glm(Revenue ~ BounceRates +  Administrative + Browser + ProductRelated, data = df, family = 'binomial')
summary(model.logit)
## 
## Call:
## glm(formula = Revenue ~ BounceRates + Administrative + Browser + 
##     ProductRelated, family = "binomial", data = df)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.1891  -0.6274  -0.5755  -0.2276   3.9437  
## 
## Coefficients:
##                  Estimate Std. Error z value Pr(>|z|)    
## (Intercept)    -1.792e+00  5.482e-02 -32.695  < 2e-16 ***
## BounceRates    -3.040e+01  2.539e+00 -11.975  < 2e-16 ***
## Administrative  4.858e-02  7.774e-03   6.248 4.15e-10 ***
## Browser         4.128e-02  1.470e-02   2.807    0.005 ** 
## ProductRelated  4.589e-03  5.376e-04   8.537  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 9605.7  on 11096  degrees of freedom
## Residual deviance: 8930.5  on 11092  degrees of freedom
## AIC: 8940.5
## 
## Number of Fisher Scoring iterations: 7
p.logit <- predict(model.logit, df, type = 'response')
Прогноз <- factor(ifelse(p.logit > 0.5, 2, 1),
                  levels = c(1, 2),
                  labels = c('FALSE', 'TRUE'))

# матрица неточностей
conf.m <- table(Факт, Прогноз)
conf.m
##        Прогноз
## Факт    FALSE TRUE
##   FALSE  9330   37
##   TRUE   1711   19
model.lda <- lda(Revenue ~ BounceRates +  Administrative + Browser + ProductRelated, data = df)
model.lda
## Call:
## lda(Revenue ~ BounceRates + Administrative + Browser + ProductRelated, 
##     data = df)
## 
## Prior probabilities of groups:
##    FALSE     TRUE 
## 0.844102 0.155898 
## 
## Group means:
##       BounceRates Administrative  Browser ProductRelated
## FALSE  0.02551689       2.114765 2.330202       28.78659
## TRUE   0.00520270       3.440462 2.469942       48.73642
## 
## Coefficients of linear discriminants:
##                         LD1
## BounceRates    -11.14537812
## Administrative   0.10654310
## Browser          0.08539891
## ProductRelated   0.01114260
p.lda <- predict(model.lda, df, type = 'response')
Прогноз <- factor(ifelse(p.lda$posterior[, 'TRUE'] > 0.5, 
                         2, 1),
                  levels = c(1, 2),
                  labels = c('FALSE', 'TRUE'))

# матрица неточностей
conf.m <- table(Факт, Прогноз)
conf.m
##        Прогноз
## Факт    FALSE TRUE
##   FALSE  9292   75
##   TRUE   1690   40
x <- NULL    # для (1 - SPC)
y <- NULL    # для TPR

# заготовка под матрицу неточностей
tbl <- as.data.frame(matrix(rep(0, 4), 2, 2))
rownames(tbl) <- c('fact.FALSE', 'fact.TRUE')
colnames(tbl) <- c('predict.FALSE', 'predict.TRUE')

# вектор вероятностей для перебора
p.vector <- seq(0, 1, length = 501)

# цикл по вероятностям отсечения
for (p in p.vector){
  # прогноз
  Прогноз <- factor(ifelse(p.lda$posterior[, 'TRUE'] > p, 
                           2, 1),
                    levels = c(1, 2),
                    labels = c('FALSE', 'TRUE'))
  
  # фрейм со сравнением факта и прогноза
  df.compare <- data.frame(Факт = Факт, Прогноз = Прогноз)
  
  # заполняем матрицу неточностей
  tbl[1, 1] <- nrow(df.compare[df.compare$Факт == 'FALSE' & df.compare$Прогноз == 'FALSE', ])
  tbl[2, 2] <- nrow(df.compare[df.compare$Факт == 'TRUE' & df.compare$Прогноз == 'TRUE', ])
  tbl[1, 2] <- nrow(df.compare[df.compare$Факт == 'FALSE' & df.compare$Прогноз == 'TRUE', ])
  tbl[2, 1] <- nrow(df.compare[df.compare$Факт == 'TRUE' & df.compare$Прогноз == 'FALSE', ])
  
  # считаем характеристики
  TPR <- tbl[2, 2] / sum(tbl[2, 2] + tbl[2, 1])
  y <- c(y, TPR)
  SPC <- tbl[1, 1] / sum(tbl[1, 1] + tbl[1, 2])
  x <- c(x, 1 - SPC)
}

# строим ROC-кривую
par(mar = c(5, 5, 1, 1))
# кривая
plot(x, y, type = 'l', col = 'blue', lwd = 3,
     xlab = '(1 - SPC)', ylab = 'TPR', 
     xlim = c(0, 1), ylim = c(0, 1))
# прямая случайного классификатора
abline(a = 0, b = 1, lty = 3, lwd = 2)

# точка для вероятности 0.5
points(x[p.vector == 0.5], y[p.vector == 0.5], pch = 16)
text(x[p.vector == 0.5], y[p.vector == 0.5], 'p = 0.5', pos = 4)
# точка для вероятности 0.2
points(x[p.vector == 0.2], y[p.vector == 0.2], pch = 16)
text(x[p.vector == 0.2], y[p.vector == 0.2], 'p = 0.2', pos = 4)

Видно, что изменение границы отсечения с 0.5 до 0.2 увеличивает чувствительность модели почти в три раза, в то же время и специфичность значительно ухудшается.