library(MASS)
library(psych)
library(klaR)
library(ICSNP)
library(mvnormtest)
library(HDMD)
library(biotools)
## ---
## biotools version 3.0
library(Hotelling)
library(candisc)
df <- read.csv("/home/ekaterina/Downloads/winequality-red_good.csv")
#df$quality <- as.factor(df$quality)
df1 <- df[,-c(1,3,5,6,8)]
head(df1)
## volatile.acidity residual.sugar total.sulfur.dioxide pH sulphates
## 1 0.70 1.9 34 3.51 0.56
## 2 0.88 2.6 67 3.20 0.68
## 3 0.76 2.3 54 3.26 0.65
## 4 0.28 1.9 60 3.16 0.58
## 5 0.70 1.9 34 3.51 0.56
## 6 0.66 1.8 40 3.51 0.56
## alcohol quality
## 1 9.4 5
## 2 9.8 5
## 3 9.8 5
## 4 9.8 6
## 5 9.4 5
## 6 9.4 5
residual sugar – остаточный сахар. Чем меньше, тем лучше, это означает, что скорее всего не добавляли сахарных сиропов и прочего, ведь такого в вине не должно быть в принципе
pH – показатель кислотности
total sulfur dioxide – полное содержание оксида серы (поступает из винограда и добавляют в качестве пищевой добавки-консерванта)
sulphates – содержание сульфатов (минеральная соль) – чем больше, тем лучше
volatile acidity – летучая кислотность (кислотность летучих кислот) – чем меньше, тем лучше, ибо повышенное содержание говорит о том, что вино скисало или имеет место быть наличие бактерий
quality - качество вина по шкале от 3 до 8
pairs.panels(df1, lm=TRUE, bg=c("red","yellow","blue", "pink", "green")[df1$quality], pch=21, lwd = 0)
Вин среднего качества больше всего и они перекрывают вины более низкого и высокого качества.
Прологорифмируем некоторые переменные
df1$volatile.acidity <- log(df1$volatile.acidity)
df1$residual.sugar <- log(df1$residual.sugar)
df1$sulphates <- log(df1$sulphates)
df1$total.sulfur.dioxide <- log(df1$total.sulfur.dioxide)
df1$alcohol <- log(df1$alcohol)
pairs.panels(df1, lm=TRUE, bg=c("red","yellow","blue", "pink", "green")[df1$quality], pch=21, lwd = 0)
normTest <- function (x){
res <- shapiro.test(x)
return(c(res$statistic, p.value = res$p.value)) }
t(sapply(df1, normTest))
## W p.value
## volatile.acidity 0.9864417 4.210479e-11
## residual.sugar 0.8550736 5.471063e-36
## total.sulfur.dioxide 0.9899255 4.632268e-09
## pH 0.9934863 1.712237e-06
## sulphates 0.9588877 7.623993e-21
## alcohol 0.9464349 1.155071e-23
## quality 0.8575895 9.515085e-36
Гипотезы о нормальном распределении отвергаются для всех признаков. Однако, это не так страшно, потому что все выводы остаются верны асимптотически.
quality <- df1[,7]
Y <- df1[,-7]
boxM(Y, quality)
##
## Box's M-test for Homogeneity of Covariance Matrices
##
## data: Y
## Chi-Sq (approx.) = 294.48, df = 105, p-value < 2.2e-16
Гипотеза о гомоскедастичности отвергается.
quality1 <- as.factor(quality)
md <- pairwise.mahalanobis(Y, quality1)$distance
colnames(md) <- levels(quality1)
rownames(md) <- levels(quality1)
print(md, digits = 3)
## 3 4 5 6 7 8
## 3 0.000 0.171 0.8939 0.6755 0.7597 0.6526
## 4 0.171 0.000 0.3006 0.1821 0.3161 0.2609
## 5 0.894 0.301 0.0000 0.0995 0.4035 0.3814
## 6 0.675 0.182 0.0995 0.0000 0.1099 0.0989
## 7 0.760 0.316 0.4035 0.1099 0.0000 0.0196
## 8 0.653 0.261 0.3814 0.0989 0.0196 0.0000
Расстояние между 5 и 6, 6 и 7, 6 и 8, 7 и 8 очень маленькое, почти ноль. То есть вины выше среднего почти не различаются по расстояние Махалонобиса, чего не наблюдается у вин ниже среднего. Инетересно, что наибольшее расстояние между 3 и 5 качеством, а не между 3 и 8, как можно было этого ожидать.
fit <- manova(as.matrix(Y)~quality)
summary(fit, test = "Wilks")
## Df Wilks approx F num Df den Df Pr(>F)
## quality 1 0.65108 142.2 6 1592 < 2.2e-16 ***
## Residuals 1597
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Гипотеза о значимости дискриминантного анализа не отвергается
Предполагаем гомоскедастичность, хотя гипотеза у нас отверглась
Везде будем использовать априорные вероятности пропорциональные количеству индивидов в группе, потому что у нас группы очень сильно различаются по объему. Они идут по умолчанию в методе lda
#### LDA ####
wine.lda <- lda(Y, quality)
wine.lda
## Call:
## lda(Y, quality)
##
## Prior probabilities of groups:
## 3 4 5 6 7 8
## 0.006253909 0.033145716 0.425891182 0.398999375 0.124452783 0.011257036
##
## Group means:
## volatile.acidity residual.sugar total.sulfur.dioxide pH sulphates
## 3 -0.1845772 0.8601003 3.011119 3.398000 -0.5807948
## 4 -0.4197593 0.8743572 3.309623 3.381509 -0.5625070
## 5 -0.5920088 0.8489759 3.795272 3.304949 -0.5049719
## 6 -0.7535112 0.8295904 3.528611 3.318072 -0.4148078
## 7 -0.9680623 0.9127478 3.302340 3.290754 -0.3160045
## 8 -0.9051405 0.8616992 3.279611 3.267222 -0.2739620
## alcohol
## 3 2.294936
## 4 2.324793
## 5 2.289943
## 6 2.358881
## 7 2.435847
## 8 2.487785
##
## Coefficients of linear discriminants:
## LD1 LD2 LD3 LD4
## volatile.acidity -1.18305241 -1.73817511 1.3454262 1.8332402
## residual.sugar 0.09120509 -0.38436959 1.3136576 -2.1942244
## total.sulfur.dioxide -0.33302480 1.04499204 0.1629951 0.5860921
## pH -0.62480306 -1.95217133 -5.0961882 -2.0084859
## sulphates 1.72875250 -0.02314187 -0.8756144 1.0528026
## alcohol 8.80996913 -1.41198452 4.3805380 5.4975821
## LD5
## volatile.acidity 0.60167850
## residual.sugar -0.01722428
## total.sulfur.dioxide -0.67846682
## pH -1.99964105
## sulphates 3.39886384
## alcohol -5.30074204
##
## Proportion of trace:
## LD1 LD2 LD3 LD4 LD5
## 0.9094 0.0726 0.0101 0.0066 0.0014
Качество классификации
wine.ldap <- predict(wine.lda, Y)$class
t1 <- table(wine.ldap, quality) #classification quality
t1
## quality
## wine.ldap 3 4 5 6 7 8
## 3 1 0 0 0 0 0
## 4 2 1 2 0 0 0
## 5 6 34 512 229 11 0
## 6 1 17 161 345 107 9
## 7 0 1 6 64 81 9
## 8 0 0 0 0 0 0
Доля ошибок
1-signif(diag(prop.table(t1, 2)),3)
## 3 4 5 6 7 8
## 0.9000 0.9811 0.2480 0.4590 0.5930 1.0000
Качество классификации оставляет желать лучшего. В 8 группу не поплал никто, однако полностью правильно определена 3 группа, хоть туда и попали индивиды из других групп. Больше всего ошибок у 4 группы. Она оказалась распределена между 3 и 5.
wine.lda.cv <- lda(Y, quality, CV = TRUE)
t2 <- table(wine.lda.cv$class, quality)
t2
## quality
## 3 4 5 6 7 8
## 3 0 0 0 0 0 0
## 4 3 0 2 0 0 0
## 5 6 34 510 230 11 0
## 6 1 18 163 342 110 9
## 7 0 1 6 66 78 9
## 8 0 0 0 0 0 0
Доля ошибок
1-signif(diag(prop.table(t2, 2)), 3)
## 3 4 5 6 7 8
## 1.000 1.000 0.251 0.464 0.608 1.000
Проверка по cross-validation показала совсем плохие результаты. 3 и 8 класс отсутствуют, в 4 никто правильно не попал.
Используем теперь предположение о гетероскедастичности модели.
##### QDA #####
wine.qda <- qda(Y, quality)
wine.qda
## Call:
## qda(Y, quality)
##
## Prior probabilities of groups:
## 3 4 5 6 7 8
## 0.006253909 0.033145716 0.425891182 0.398999375 0.124452783 0.011257036
##
## Group means:
## volatile.acidity residual.sugar total.sulfur.dioxide pH sulphates
## 3 -0.1845772 0.8601003 3.011119 3.398000 -0.5807948
## 4 -0.4197593 0.8743572 3.309623 3.381509 -0.5625070
## 5 -0.5920088 0.8489759 3.795272 3.304949 -0.5049719
## 6 -0.7535112 0.8295904 3.528611 3.318072 -0.4148078
## 7 -0.9680623 0.9127478 3.302340 3.290754 -0.3160045
## 8 -0.9051405 0.8616992 3.279611 3.267222 -0.2739620
## alcohol
## 3 2.294936
## 4 2.324793
## 5 2.289943
## 6 2.358881
## 7 2.435847
## 8 2.487785
Качество классификации
wine.qdap <- predict(wine.qda, Y)$class
t3 <- table(wine.qdap, quality) #classification quality
t3
## quality
## wine.qdap 3 4 5 6 7 8
## 3 4 0 4 1 0 0
## 4 0 5 5 7 0 0
## 5 5 33 533 246 18 0
## 6 1 14 130 343 102 11
## 7 0 1 9 41 75 4
## 8 0 0 0 0 4 3
Доля ошибок
1 - signif(diag(prop.table(t3, 2)),3)
## 3 4 5 6 7 8
## 0.6000 0.9057 0.2170 0.4620 0.6230 0.8330
Качество классификаии радует чуть больше, потому что у нас хотя бы появились 3 и 8 классы. Однако, количество верно определенных индивидов все еще очень небольшое.
wine.qda.cv <- qda(Y, quality, CV = TRUE)
t4 <- table(wine.qda.cv$class, quality)
t4
## quality
## 3 4 5 6 7 8
## 3 0 0 5 1 0 0
## 4 2 1 5 7 0 0
## 5 7 36 527 252 18 0
## 6 1 14 135 334 106 11
## 7 0 2 9 44 69 6
## 8 0 0 0 0 6 1
Доля ошибок
1 - signif(diag(prop.table(t4, 2)), 3)
## 3 4 5 6 7 8
## 1.0000 0.9811 0.2260 0.4760 0.6530 0.9444
quality.factor <- as.factor(quality)
Проверка по cross-validation практически стерла 3 и 4 классы.
Делаем вывод, что проблемные классы - это наиболее плохое и хорошее качество вина (3,4 и 8). Принадлежность к остальным определяет примерно одниково для всех методов и проверок и составляет около 50% верных попаданий.
Разделим теперь выборку на обучающую и тестируемую.
#### LDA, out-of-sample ####
wine.train <- df1[-seq(1, nrow(df1), 5),]
wine.unknown <- df1[seq(1,nrow(df1),5),]
wine.lda2 <- lda(wine.train[,1:6], wine.train[,7])
wine.lda2p <- predict(wine.lda2, wine.unknown[,1:6])$class
t5 <- table(wine.lda2p, wine.unknown[,7])
signif(prop.table(t5,1), 3)
##
## wine.lda2p 3 4 5 6 7 8
## 3
## 4 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000
## 5 0.0061 0.0183 0.6950 0.2800 0.0000 0.0000
## 6 0.0000 0.0161 0.2020 0.5560 0.1940 0.0323
## 7 0.0000 0.0323 0.0000 0.4520 0.4520 0.0645
## 8
signif(diag(prop.table(t5,1)), 3)
## 3 4 5 6 7 8
## NaN 0.000 0.695 0.556 0.452 NaN
Качество классификации не улучшилось.
Объединим группы по расстоянию Махаланобиса. Пусть их будет 3:
1 - низкое качество 3 и 4
2 - среднее 5 и 6
3 - высокое 7 и 8
df1$new_quality <- as.factor(df1$quality)
levels(df1$new_quality) <- c(1,1,2,2,3,3)
#df1$new_quality <- as.numeric(df1$new_quality)
quality2 <- df1[,8]
pairs.panels(df1[,-7], bg=c("red","yellow","blue")[df1$new_quality], pch=21, lwd = 0)
Количество индивидов по группам
sapply(levels(df1$new_quality), function(level){
nrow(df1[df1$new_quality == level,])
})
## 1 2 3
## 63 1319 217
manova_wine1 <- manova(as.matrix(df1[-c(7,8)])~df1$new_quality, data = df1)
summary(manova_wine1, test = "Wilks")
## Df Wilks approx F num Df den Df Pr(>F)
## df1$new_quality 2 0.72537 46.177 12 3182 < 2.2e-16 ***
## Residuals 1596
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Дискриминантный анализ значим
greedy.wilks(df1[-c(7,8)], quality2)
## Formula containing included variables:
##
## quality2 ~ alcohol + volatile.acidity + sulphates + total.sulfur.dioxide +
## pH + residual.sugar
## <environment: 0x8397120>
##
##
## Values calculated in each step of the selection procedure:
##
## vars Wilks.lambda F.statistics.overall p.value.overall
## 1 alcohol 0.8356592 156.93477 6.015734e-63
## 2 volatile.acidity 0.7702488 111.18878 7.601321e-89
## 3 sulphates 0.7515643 81.55872 3.173240e-95
## 4 total.sulfur.dioxide 0.7353268 66.17501 8.871697e-101
## 5 pH 0.7283892 54.67085 3.482194e-102
## 6 residual.sugar 0.7253675 46.17689 7.430611e-102
## F.statistics.diff p.value.diff
## 1 156.934772 6.015734e-63
## 2 67.724532 0.000000e+00
## 3 19.814099 3.162325e-09
## 4 17.588387 2.783078e-08
## 5 7.581537 5.283871e-04
## 6 3.313825 3.662787e-02
Не значим residual.sugar. Уберем его и посмотрим, как изменилась статистика
MANOVA еще раз
manova_wine2 <- manova(as.matrix(df1[-c(2,7,8)])~df1$new_quality, data = df1)
summary(manova_wine2, test = "Wilks")
## Df Wilks approx F num Df den Df Pr(>F)
## df1$new_quality 2 0.72839 54.671 10 3184 < 2.2e-16 ***
## Residuals 1596
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Чуть-чуть увеличилась статистика. Осталось значимо
df2 <- df1[,-c(2,7)]
#Между 1 и 2 группой
print(hotelling.test(.~new_quality, data = df2, pair = c(1,2)))
## Test stat: 15.454
## Numerator df: 5
## Denominator df: 1376
## P-value: 8.216e-15
#Между 1 и 3 группой
print(hotelling.test(.~new_quality, data = df2, pair = c(1,3)))
## Test stat: 62.59
## Numerator df: 5
## Denominator df: 274
## P-value: 0
#Между 2 и 3 группой
print(hotelling.test(.~new_quality, data = df2, pair = c(2,3)))
## Test stat: 95.027
## Numerator df: 5
## Denominator df: 1530
## P-value: 0
Все группы значимо различаются между собой
cd <- candisc(manova_wine2, data = df1[,-c(2,7)])
summary(cd)
##
## Canonical Discriminant Analysis for df1$new_quality:
##
## CanRsq Eigenvalue Difference Percent Cumulative
## 1 0.242718 0.320511 0.28084 88.987 88.987
## 2 0.038154 0.039667 0.28084 11.013 100.000
##
## Class means:
##
## Can1 Can2
## 1 -0.72635 -0.948692
## 2 -0.19623 0.060328
## 3 1.40360 -0.091266
##
## std coefficients:
## Can1 Can2
## volatile.acidity -0.38640 -0.574567
## total.sulfur.dioxide -0.12495 0.704223
## pH -0.19536 -0.204733
## sulphates 0.31366 0.028481
## alcohol 0.73549 -0.257746
cd$coeffs.std
## Can1 Can2
## volatile.acidity -0.3864049 -0.57456671
## total.sulfur.dioxide -0.1249535 0.70422272
## pH -0.1953590 -0.20473323
## sulphates 0.3136629 0.02848083
## alcohol 0.7354916 -0.25774632
cd$structure
## Can1 Can2
## volatile.acidity -0.6494908 -0.5187061
## total.sulfur.dioxide -0.2957879 0.7163536
## pH -0.1480944 -0.4073705
## sulphates 0.5030593 0.2354997
## alcohol 0.8113886 -0.3452305
print(cd)
##
## Canonical Discriminant Analysis for df1$new_quality:
##
## CanRsq Eigenvalue Difference Percent Cumulative
## 1 0.242718 0.320511 0.28084 88.987 88.987
## 2 0.038154 0.039667 0.28084 11.013 100.000
##
## Test of H0: The canonical correlations in the
## current row and all that follow are zero
##
## LR test stat approx F numDF denDF Pr(> F)
## 1 0.72839 54.671 10 3184 < 2.2e-16 ***
## 2 0.96185 15.797 4 1593 1.098e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Все значимо
plot(cd)
## Vector scale factor set to 4.586
На графике видно, как сильно перемешана 1 группа (зеленый цвет) со всеми остальными. Это может объяснить очень плохую классификацию по 1 группе.
Сравним между собой только QDA и LDA. Будем использовать априорные вероятности, пропорциональные объему групп.
Y2 <- df1[-c(2,7,8)]
wine.lda <- lda(Y2, quality2)
wine.lda
## Call:
## lda(Y2, quality2)
##
## Prior probabilities of groups:
## 1 2 3
## 0.03939962 0.82489056 0.13570982
##
## Group means:
## volatile.acidity total.sulfur.dioxide pH sulphates alcohol
## 1 -0.3824288 3.262241 3.384127 -0.5654098 2.320054
## 2 -0.6701275 3.666288 3.311296 -0.4613595 2.323288
## 3 -0.9628430 3.300454 3.288802 -0.3125171 2.440156
##
## Coefficients of linear discriminants:
## LD1 LD2
## volatile.acidity -1.1590714 1.7234871
## total.sulfur.dioxide -0.1807566 -1.0187218
## pH -1.2720321 1.3330697
## sulphates 1.4489051 -0.1315617
## alcohol 8.1301265 2.8491288
##
## Proportion of trace:
## LD1 LD2
## 0.8899 0.1101
wine.ldap <- predict(wine.lda, Y2)$class
t1 <- table(wine.ldap, quality2)
t1
## quality2
## wine.ldap 1 2 3
## 1 1 2 0
## 2 62 1254 133
## 3 0 63 84
Доля ошибок
1-signif(diag(prop.table(t1, 2)),3)
## 1 2 3
## 0.9841 0.0490 0.6130
На матриксплоте видно насколько смешаны данные. Было бы странно ожидать каких-то иных результатов. Очень многие индивиды были ошибочно приписаны ко 2 группе.
Попарный дискриминантный анализ.
partimat(new_quality ~ ., df2, method="lda")
Здесь видно, что по некоторым признакам 1 группа вообще не представлена.
wine.qda <- qda(Y2, quality2)
wine.qda
## Call:
## qda(Y2, quality2)
##
## Prior probabilities of groups:
## 1 2 3
## 0.03939962 0.82489056 0.13570982
##
## Group means:
## volatile.acidity total.sulfur.dioxide pH sulphates alcohol
## 1 -0.3824288 3.262241 3.384127 -0.5654098 2.320054
## 2 -0.6701275 3.666288 3.311296 -0.4613595 2.323288
## 3 -0.9628430 3.300454 3.288802 -0.3125171 2.440156
wine.qdap <- predict(wine.qda, Y2)$class
t2 <- table(wine.qdap, quality2)
t2
## quality2
## wine.qdap 1 2 3
## 1 6 9 0
## 2 57 1237 125
## 3 0 73 92
Доля ошибок
1-signif(diag(prop.table(t2, 2)),3)
## 1 2 3
## 0.9048 0.0620 0.5760
Доля ошибок чуть меньше при квадратичной классифицирующей функции
Попарный дискриминантный анализ.
partimat(new_quality ~ ., df2, method="qda")
Сравним между собой только QDA и LDA. Будем использовать априорные вероятности, равные между собой.
Y2 <- df1[-c(2,7,8)]
wine.lda <- lda(Y2, quality2, prior = c(1/3,1/3,1/3))
wine.lda
## Call:
## lda(Y2, quality2, prior = c(1/3, 1/3, 1/3))
##
## Prior probabilities of groups:
## 1 2 3
## 0.3333333 0.3333333 0.3333333
##
## Group means:
## volatile.acidity total.sulfur.dioxide pH sulphates alcohol
## 1 -0.3824288 3.262241 3.384127 -0.5654098 2.320054
## 2 -0.6701275 3.666288 3.311296 -0.4613595 2.323288
## 3 -0.9628430 3.300454 3.288802 -0.3125171 2.440156
##
## Coefficients of linear discriminants:
## LD1 LD2
## volatile.acidity -1.6472502 1.2650775
## total.sulfur.dioxide 0.1528035 -1.0232879
## pH -1.6301057 0.8590087
## sulphates 1.4154567 0.3363287
## alcohol 6.8009194 5.2880986
##
## Proportion of trace:
## LD1 LD2
## 0.8836 0.1164
wine.ldap <- predict(wine.lda, Y2)$class
t1 <- table(wine.ldap, quality2)
t1
## quality2
## wine.ldap 1 2 3
## 1 43 340 20
## 2 15 693 21
## 3 5 286 176
Доля ошибок
1-signif(diag(prop.table(t1, 2)),3)
## 1 2 3
## 0.317 0.475 0.189
Ошибок в классификации 1 и 3 групп стало значительно меньше (в 3 раза), но при этом существенно увеличилось количество ошибок в классификации 2 группы (в 5 раз).
wine.qda <- qda(Y2, quality2, prior = c(1/3,1/3,1/3))
wine.qda
## Call:
## qda(Y2, quality2, prior = c(1/3, 1/3, 1/3))
##
## Prior probabilities of groups:
## 1 2 3
## 0.3333333 0.3333333 0.3333333
##
## Group means:
## volatile.acidity total.sulfur.dioxide pH sulphates alcohol
## 1 -0.3824288 3.262241 3.384127 -0.5654098 2.320054
## 2 -0.6701275 3.666288 3.311296 -0.4613595 2.323288
## 3 -0.9628430 3.300454 3.288802 -0.3125171 2.440156
wine.qdap <- predict(wine.qda, Y2)$class
t2 <- table(wine.qdap, quality2)
t2
## quality2
## wine.qdap 1 2 3
## 1 43 286 16
## 2 18 734 25
## 3 2 299 176
Доля ошибок
1-signif(diag(prop.table(t2, 2)),3)
## 1 2 3
## 0.317 0.444 0.189
Качество классификации при квадратичной функции не изменилось по сравнению с линейной функцией