В описанных ниже примерах использовались две модели: одна с явным разделяющим порогом, вторая – без него. Эти две модели, конечно, вряд ли описывают реальные объекты, но позволяют приблизительно оценить свойства получаемых при дихотомизации количественного предиктора оценок. Сама по себе дихотомизация предикторов (равно как и исходов) негативно воспринимается многими известными статистиками [Harrell, Senn] и в реальных исследованиях ее нужно избегать.
nsim <- 1000
NB! Есть два существенных замечания по поводу результатов представленных ниже симуляций. 1) я для простоты целенаправленно проводил их так, чтобы избежать значительного дисбаланса классов, поэтому результаты больше имеют отношение к исследованиям типа случай-контроль; 2) использованные методы определения оптимальных порогов не всегда приводят к однозначным резульататам, поэтому при наличии двух возможных порогов (для одного метода) я выбирал тот, который меньше отличается от теоретического порога.
Все симуляции проводились с размером выборки \(N = 100\), количество выборок = 1000.
В данной модели переменная отклик (\(Y\)) имеет условную функцию рапределения следующего вида:
\[ P(Y=y | X=x) = \begin{cases} 0.8^y(1-0.8)^{1-y}I_{\{x \ge 100\}}\\0.2^y(1-0.2)^{1-y}I_{\{x < 100\}} \end{cases} \]
где \(Y \in \{0, 1\}\) и \(X \sim N(100, 20^{2})\), т.е. \(Y\) принимает значение 1 с вероятностью 0.8, если \(X \ge 100\) (и 0 иначе) и значение 1 с вероятность 0.2 если \(X<100\). Независимая переменная \(X\) имеет нормальное распределение с матожиданием \(\mu = 100\) и среднеквадратичным отклонением \(\sigma =20\).
Несложно провести расчет отношения шансов:
\[OR = \frac{Odds_{(x \ge 100)}}{Odds_{(x < 100)}} = \frac{0.8/(1-0.8)}{0.2/(1-0.2)} = 16\]
Кроме того при испрользовании порога \(X = 100\) в качестве порога, можно оцйенить диагностические свойства.
\[P(Y = 0|X < 100) = 0.2^0(1-0.2)^1 \times 0.5 = 0.4\]
\[P(Y = 0|X \ge 100) = 0.8^0(1-0.8)^1 \times 0.5 = 0.1\]
\[P(Y = 1|X \ge 100) = 0.8^1(1-0.8)^0 \times 0.5 = 0.4\]
\[P(Y = 1|X < 100) = 0.2^1(1-0.2)^0 \times 0.5 = 0.1\]
\[P(Y = 0|X < 100) + P(Y = 1|X \ge 100) = 0.8\]
n <- 100000
x <- rnorm(n, 100, 20)
y <- sapply(x, function(x) ifelse(x >= 100, rbinom(1, 1, 0.8), rbinom(1, 1, 0.2)))
df <- data.frame(X = x, Xge100 = as.numeric(x >= 100), Y = y)
Дополним расчет, симуляцией (классы сбалансированы: \(N_{(Y=1)} \approx N_{(Y=0)}\) при \(N_{(X\ge 100)} + N_{(X<100)} \to \infty\)). Для начала посмотрим на распрелеление и сравним полученные выше результаты с результатами при \(N_{(X\ge 100)} + N_{(X<100)} = 10^{5}\).
plot(jitter(df$Y, factor = 0.5) ~ df$X, yaxt="n",
col = rgb(0.15, 0.17, 0.18, alpha = 0.05),
pch = 20, cex = 0.2, xlab = 'X', ylab = 'Y')
axis(2, at = c(0, 1), labels = c('0', '1'), las = 2)
abline(v = 100, col = 'red')
Y <- factor(df$Y, labels = c('Y=0', 'Y=1'))
confusion <- (table(df$Xge100, Y)/n)
cbind( c('X<100', 'X≥100'), confusion) %>%
kable(row.names = F, align = 'c', caption = 'Матрица ошибок(Confusion matrix)') %>%
kable_styling(full_width = F, bootstrap_options = 'bordered') %>%
column_spec(1, bold = T)
| Y=0 | Y=1 | |
|---|---|---|
| X<100 | 0.40281 | 0.09892 |
| X≥100 | 0.10019 | 0.39808 |
Теперь посмотрим на ROC-кривую, применим два подхода к определению порога.
roc_fit <- roc(df$Y, df$X, ci = F, show.thres = T, auc = T)
plot(roc_fit, ylab="TPR", xlab="TNR")
AUC = 0.800482
Два метода определения порогов:
\[max[Чувствительность + Специфичность]\]
\[min[(1-Чувствительность)^2 + (1-Специфичность)^2]\]
rbind(
coords(roc_fit, "best", best.method="youden",
ret=c("threshold", "sens", "spec",'accuracy')),
coords(roc_fit, "best", best.method="closest.topleft",
ret=c("threshold", "sens", "spec",'accuracy'))
) %>% t() %>% as.data.frame() -> thresh
fit1 <- glm(factor(df$Y) ~ factor(as.numeric(df$X>=thresh[1,1])), family = 'binomial')
fit2 <- glm(factor(df$Y) ~ factor(as.numeric(df$X>=thresh[1,2])), family = 'binomial')
colnames(thresh) <- c("Youden's J", "TopLeft")
rbind(
thresh[1,],
c(exp(coef(fit1))[2], exp(coef(fit2))[2]),
thresh[2:4,]) -> thresh
rownames(thresh) <- c('Порог','Отношение шансов','Чувствительность','Специфичность','Точность')
thresh %>%
kable(row.names = T, digits = 3) %>%
kable_styling(full_width = F)
| Youden’s J | TopLeft | |
|---|---|---|
| Порог | 99.997 | 99.997 |
| Отношение шансов | 16.184 | 16.184 |
| Чувствительность | 0.801 | 0.801 |
| Специфичность | 0.801 | 0.801 |
| Точность | 0.801 | 0.801 |
Таким образом, оба метода определения порога позволяют сделать “хорошие” оценки интересующих показателей при большой выборке и сбалансированных классах.
n <- 100
result <- matrix(data = NA_real_, nrow = nsim, ncol = 12)
colnames(result) <- c('P_hat','AUC','Yoden','Sens_Yoden','Spec_Yoden','Accuracy_Yoden',
'OR_Yoden','TopLeft','Sens_TL','Spec_TL','Accuracy_TL','OR_TL')
for (i in 1:nsim) {
x <- rnorm(n, 100, 20)
y <- sapply(x, function(x) ifelse(x >= 100, rbinom(1, 1, 0.8), rbinom(1, 1, 0.2)))
roc_fit <- roc(y, x, ci = F, show.thres = T, auc = T)
p_hat <- mean(y)
AUC <- as.numeric(auc(roc_fit))
Yoden <- data.frame(coords(roc_fit, "best", best.method="youden",
ret=c("threshold", "sens", "spec",'accuracy'), transpose = F))
TopLeft <- data.frame(coords(roc_fit, "best", best.method="closest.topleft",
ret=c("threshold", "sens", "spec",'accuracy'), transpose = F))
Yoden <- Yoden[which.min(abs(100 - Yoden[,1])),]
TopLeft <- TopLeft[which.min(abs(100 - Yoden[,1])),]
OR_Yoden <- exp(coef(glm(factor(y) ~ factor(as.numeric(x>=Yoden[1,1])), family = 'binomial')))[2]
OR_TL <- exp(coef(glm(factor(y) ~ factor(as.numeric(x>=TopLeft[1,1])), family = 'binomial')))[2]
result[i,] <- unlist(c(p_hat, AUC, Yoden[1,], OR_Yoden, TopLeft[1,], OR_TL))
}
res1 <- apply(result, 2, mean_sd)
Теперь проведем симуляцию при размере выборки \(N = 100\) для того, чтобы оценить свойства распределений интересующих нас статистик.
NB! Залитые области на графиках эмпирических функций плотности соответствуют 95% интервалам наибольшей плотности (Highest density interval, HDI – 95% наиболее часто встречающихся (наиболее вероятных) значений). Пунктирными линиями обозначены матожидания соотвествующих статистик, сплошными – наблюдаемые средние по 1000 выборкам.
Рсапределения числа успехов (\(Y = 1\)) и оценок AUC.
par(mfrow = c(1,2), mar = c(5,5,1,1))
plot(density(result[, 'P_hat']), main = NA, xlab = 'Доля Y=1', ylab = 'Плотность')
polygon(density(result[, 'P_hat'])$x[density(result[, 'P_hat'])$y/max(density(result[, 'P_hat'])$y)>=0.025],
density(result[, 'P_hat'])$y[density(result[, 'P_hat'])$y/max(density(result[, 'P_hat'])$y)>=0.025],
col = rgb(0.15, 0.15, 0.15, alpha = 0.05), border = NA)
abline(v=0.5, col=rgb(0.15, 0.15, 0.15), lwd = 2)
abline(v=mean(result[, 'P_hat']), col='red', lty = 2, lwd = 2)
plot(density(result[, 'AUC']), main = NA, xlab = 'AUC', ylab = 'Плотность')
polygon(density(result[, 'AUC'])$x[density(result[, 'AUC'])$y/max(density(result[, 'AUC'])$y)>=0.025],
density(result[, 'AUC'])$y[density(result[, 'AUC'])$y/max(density(result[, 'AUC'])$y)>=0.025],
col = rgb(0.15, 0.15, 0.15, alpha = 0.05), border = NA)
abline(v=0.8, col=rgb(0.15, 0.15, 0.15), lwd = 2)
abline(v=mean(result[, 'AUC']), col='red', lty = 2, lwd = 2)
Оценки порогов
par(mar = c(5,5,1,1))
plot(result[,'Yoden'], result[,'TopLeft'], col = rgb(0.15, 0.17, 0.18, alpha = 0.2),
xlim = c(90,110), ylim = c(90,110), cex = 0.8, xlab = 'Yoden\'s J', ylab = 'TopLeft')
segments(100, 80, 100, 100, lwd = 2)
segments(mean(result[,'Yoden']), 80, mean(result[,'Yoden']), mean(result[,'TopLeft']),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
segments(80, 100, 100, 100, lwd = 2)
segments(80, mean(result[,'TopLeft']), mean(result[,'Yoden']), mean(result[,'TopLeft']),
col = rgb(0,1,0.7), lty = 2, lwd = 2)
rug(result[,'Yoden'], lwd = 0.5, side = 1, col = rgb(1,1,0.5, alpha = 0.2), tck = 0.05, pos = 90)
rug(result[,'TopLeft'], lwd = 0.5, side = 2, col = rgb(0,1,0.7, alpha = 0.2), tck = 0.05, pos = 90)
Оценки характеристик диагностического теста для соответсвующих порогов.
par(mfrow = c(2,2), mar = c(5,5,1,1))
plot(density(result[, 'Sens_TL']), main = NA, xlab = 'Чувствительность', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'Sens_TL'])$x[density(result[, 'Sens_TL'])$y/max(density(result[, 'Sens_TL'])$y)>=0.025]), rep(min(density(result[, 'Sens_TL'])$y[density(result[, 'Sens_TL'])$y/max(density(result[, 'Sens_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'Sens_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'Sens_Yoden'])$x[density(result[, 'Sens_Yoden'])$y/max(density(result[, 'Sens_Yoden'])$y)>=0.025]), rep(min(density(result[, 'Sens_Yoden'])$y[density(result[, 'Sens_Yoden'])$y/max(density(result[, 'Sens_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'Sens_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'Sens_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
plot(density(result[, 'Spec_TL']), main = NA, xlab = 'Специфичность', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'Spec_TL'])$x[density(result[, 'Spec_TL'])$y/max(density(result[, 'Spec_TL'])$y)>=0.025]), rep(min(density(result[, 'Spec_TL'])$y[density(result[, 'Spec_TL'])$y/max(density(result[, 'Spec_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'Spec_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'Spec_Yoden'])$x[density(result[, 'Spec_Yoden'])$y/max(density(result[, 'Spec_Yoden'])$y)>=0.025]), rep(min(density(result[, 'Spec_Yoden'])$y[density(result[, 'Spec_Yoden'])$y/max(density(result[, 'Spec_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'Spec_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'Spec_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
plot(density(result[, 'Accuracy_TL']), main = NA, xlab = 'Точность', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'Accuracy_TL'])$x[density(result[, 'Accuracy_TL'])$y/max(density(result[, 'Accuracy_TL'])$y)>=0.025]), rep(min(density(result[, 'Accuracy_TL'])$y[density(result[, 'Accuracy_TL'])$y/max(density(result[, 'Accuracy_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'Accuracy_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'Accuracy_Yoden'])$x[density(result[, 'Accuracy_Yoden'])$y/max(density(result[, 'Accuracy_Yoden'])$y)>=0.025]), rep(min(density(result[, 'Accuracy_Yoden'])$y[density(result[, 'Accuracy_Yoden'])$y/max(density(result[, 'Accuracy_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'Accuracy_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'Accuracy_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
plot(density(result[, 'OR_TL']), main = NA, xlab = 'Отношение шансов', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'OR_TL'])$x[density(result[, 'OR_TL'])$y/max(density(result[, 'OR_TL'])$y)>=0.025]), rep(min(density(result[, 'OR_TL'])$y[density(result[, 'OR_TL'])$y/max(density(result[, 'OR_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'OR_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'OR_Yoden'])$x[density(result[, 'OR_Yoden'])$y/max(density(result[, 'OR_Yoden'])$y)>=0.025]), rep(min(density(result[, 'OR_Yoden'])$y[density(result[, 'OR_Yoden'])$y/max(density(result[, 'OR_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'OR_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'OR_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
Данная модель несколько более приближена к реальности, в ней отсутствует четкая разделяющая граница между классами, зависящая от значения переменной \(X\). При дихотомизации на уровне \(X = 100\), \(p = 0.5\). Причем классы сбалансированы при данном пороге (\(N_{(Y=1)} \approx N_{(Y=0)}\) при \(N_{(X\ge 100)} + N_{(X<100)} \to \infty\)).
В целом модель имеет следующий вид:
\[log(\frac{p_i}{1-p_i}) = \beta_0 + \beta_1\times x_i + \epsilon_i\]
где \(\beta_1 = log(OR) = \frac{1}{2}\) при изменении количественного предиктора на 1, а \(e_i \sim N(0, 0.1)\).
Проверим данные утверждения на большой выборке (\(N= 100\)).
n <- 100000
proba <- seq(0.1, 0.9, length.out = n)
y <- rbinom(n, 1, proba)
logits <- log(proba/(1-proba))
x <- 100 + 2 * logits + rnorm(n, 0, 0.1)
df <- data.frame(X = x, Xge100 = as.numeric(x >= 100), Y = y)
fit <- glm(Y ~ X, df, family = 'binomial')
df$pred <- predict(fit, type = 'response')
plot(jitter(df$Y, factor = 0.5) ~ df$X, yaxt="n",
col = rgb(0.158, 0.168, 0.184, alpha = 0.05),
pch = 20, cex = 0.2, xlab = 'X', ylab = 'Y')
axis(2, at = c(0, 0.5, 1), labels = c('0','0.5', '1'), las = 2)
lines(df$X, df$pred, col = 'blue')
segments(100, -0.5, 100, 0.5, col = 'red')
segments(94, 0.5, 100, 0.5, col = 'red')
Соотношение сигнал/шум в модели
plot(df$pred, proba, type = 'l', col = rgb(0.158, 0.168, 0.184, alpha = 0.3),
xlab = 'Предсказанная вероятность Y=1', ylab = 'Вероятность Y=1')
summary(fit)$coef %>%
as.table() -> coeffs
colnames(coeffs) <- c('Оценка', 'Стандартная\nошибка', 'z-статистика', 'p-значение')
rownames(coeffs) <- c('$\\beta_0$', '$\\beta_1$')
coeffs %>%
kable(caption = 'Коэффициенты регрессионного уравнения', escape = F) %>% kable_styling(full_width = F)
| Оценка | Стандартная ошибка | z-статистика | p-значение | |
|---|---|---|---|---|
| \(\beta_0\) | -50.0474400 | 0.3754004 | -133.3175 | 0 |
| \(\beta_1\) | 0.5004046 | 0.0037531 | 133.3304 | 0 |
roc_fit <- roc(y, x, ci = F, show.thres = T)
plot(roc_fit, ylab="TPR", xlab="TNR")
auc_num <- as.numeric(auc(roc_fit))
AUC = 0.7669859
rbind(
coords(roc_fit, "best", best.method="youden",
ret=c("threshold", "sens", "spec",'accuracy')),
coords(roc_fit, "best", best.method="closest.topleft",
ret=c("threshold", "sens", "spec",'accuracy'))
) %>% t() %>% as.data.frame() -> thresh
fit1 <- glm(factor(df$Y) ~ factor(as.numeric(df$X>=thresh[1,1])), family = 'binomial')
fit2 <- glm(factor(df$Y) ~ factor(as.numeric(df$X>=thresh[1,2])), family = 'binomial')
colnames(thresh) <- c("Youden's J", "TopLeft")
rbind(
thresh[1,],
c(exp(coef(fit1))[2], exp(coef(fit2))[2]),
thresh[2:4,]) -> thresh
rownames(thresh) <- c('Порог','Отношение шансов','Чувствительность','Специфичность','Точность')
thresh %>%
kable(row.names = T, digits = 3) %>%
kable_styling(full_width = F)
| Youden’s J | TopLeft | |
|---|---|---|
| Порог | 99.934 | 100.012 |
| Отношение шансов | 5.493 | 5.483 |
| Чувствительность | 0.711 | 0.699 |
| Специфичность | 0.690 | 0.702 |
| Точность | 0.701 | 0.701 |
n <- 100
result <- matrix(data = NA_real_, nrow = nsim, ncol = 13)
colnames(result) <- c('P_hat','AUC','OR','Yoden','Sens_Yoden','Spec_Yoden','Accuracy_Yoden',
'OR_Yoden','TopLeft','Sens_TL','Spec_TL','Accuracy_TL','OR_TL')
for (i in 1:nsim) {
proba <- seq(0.1, 0.9, length.out = n)
y <- rbinom(n, 1, proba)
logits <- log(proba/(1-proba))
x <- 100 + 2 * logits + rnorm(n, 0, 0.1)
roc_fit <- roc(y, x, ci = F, show.thres = T, auc = T)
p_hat <- mean(y)
AUC <- as.numeric(auc(roc_fit))
OR <- exp(coef(glm(factor(y) ~ x, family = 'binomial')))[2]
Yoden <- data.frame(coords(roc_fit, "best", best.method="youden",
ret=c("threshold", "sens", "spec",'accuracy'), transpose = F))
TopLeft <- data.frame(coords(roc_fit, "best", best.method="closest.topleft",
ret=c("threshold", "sens", "spec",'accuracy'), transpose = F))
Yoden <- Yoden[which.min(abs(100 - Yoden[,1])),]
TopLeft <- TopLeft[which.min(abs(100 - Yoden[,1])),]
OR_Yoden <- exp(coef(glm(factor(y) ~ factor(as.numeric(x>=Yoden[1,1])), family = 'binomial')))[2]
OR_TL <- exp(coef(glm(factor(y) ~ factor(as.numeric(x>=TopLeft[1,1])), family = 'binomial')))[2]
result[i,] <- unlist(c(p_hat, AUC, OR, Yoden[1,], OR_Yoden, TopLeft[1,], OR_TL))
}
res2 <- apply(result, 2, mean_sd)[-3]
Распределения числа успехов (\(Y = 1\)), оценок OR для количественного предиктора и оценок AUC.
par(mfrow = c(1,3), mar = c(5,5,1,1))
plot(density(result[, 'P_hat']), main = NA, xlab = 'Доля Y=1', ylab = 'Плотность')
polygon(density(result[, 'P_hat'])$x[density(result[, 'P_hat'])$y/max(density(result[, 'P_hat'])$y)>=0.025],
density(result[, 'P_hat'])$y[density(result[, 'P_hat'])$y/max(density(result[, 'P_hat'])$y)>=0.025],
col = rgb(0.15, 0.15, 0.15, alpha = 0.05), border = NA)
abline(v=0.5, col=rgb(0.15, 0.15, 0.15), lwd = 2)
abline(v=mean(result[, 'P_hat']), col='red', lty = 2, lwd = 2)
plot(density(result[, 'OR']), main = NA, xlab = 'OR', ylab = 'Плотность')
polygon(density(result[, 'OR'])$x[density(result[, 'OR'])$y/max(density(result[, 'OR'])$y)>=0.025],
density(result[, 'OR'])$y[density(result[, 'OR'])$y/max(density(result[, 'OR'])$y)>=0.025],
col = rgb(0.15, 0.15, 0.15, alpha = 0.05), border = NA)
abline(v=exp(1/2), col=rgb(0.15, 0.15, 0.15), lwd = 2)
abline(v=mean(result[, 'OR']), col='red', lty = 2, lwd = 2)
plot(density(result[, 'AUC']), main = NA, xlab = 'AUC', ylab = 'Плотность')
polygon(density(result[, 'AUC'])$x[density(result[, 'AUC'])$y/max(density(result[, 'AUC'])$y)>=0.025],
density(result[, 'AUC'])$y[density(result[, 'AUC'])$y/max(density(result[, 'AUC'])$y)>=0.025],
col = rgb(0.15, 0.15, 0.15, alpha = 0.05), border = NA)
abline(v=auc_num, col=rgb(0.15, 0.15, 0.15), lwd = 2)
abline(v=mean(result[, 'AUC']), col='red', lty = 2, lwd = 2)
Оценки порогов
par(mfrow = c(1,2), mar = c(5,5,1,1))
plot(result[,'Yoden'], result[,'TopLeft'], col = rgb(0.15, 0.17, 0.18, alpha = 0.2),
xlim = c(97,103), ylim = c(97,103), cex = 0.8, xlab = 'Yoden\'s J', ylab = 'TopLeft')
segments(100, 80, 100, 100, lwd = 2)
segments(mean(result[,'Yoden']), 80, mean(result[,'Yoden']), mean(result[,'TopLeft']),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
segments(80, 100, 100, 100, lwd = 2)
segments(80, mean(result[,'TopLeft']), mean(result[,'Yoden']), mean(result[,'TopLeft']),
col = rgb(0,1,0.7), lty = 2, lwd = 2)
rug(result[,'Yoden'], lwd = 0.5, side = 1, col = rgb(1,1,0.5, alpha = 0.2), tck = 0.05, pos = 97)
rug(result[,'TopLeft'], lwd = 0.5, side = 2, col = rgb(0,1,0.7, alpha = 0.2), tck = 0.05, pos = 97)
plot(result[, 'P_hat']-0.0025, result[,'Yoden'], col = rgb(1,1,0.5, alpha = 0.5),
pch = 20, cex = 0.5, xlab = 'Доля Y=1', ylab = 'Оценки порогов')
points(result[, 'P_hat']+0.0025, result[,'TopLeft'], col = rgb(0,1,0.7, alpha = 0.5),
pch = 20, cex = 0.5)
Обобщенные характеристики диагностического теста для соответсвующих порогов.
par(mfrow = c(2,2), mar = c(5,5,1,1))
plot(density(result[, 'Sens_TL']), main = NA, xlab = 'Чувствительность', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'Sens_TL'])$x[density(result[, 'Sens_TL'])$y/max(density(result[, 'Sens_TL'])$y)>=0.025]), rep(min(density(result[, 'Sens_TL'])$y[density(result[, 'Sens_TL'])$y/max(density(result[, 'Sens_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'Sens_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'Sens_Yoden'])$x[density(result[, 'Sens_Yoden'])$y/max(density(result[, 'Sens_Yoden'])$y)>=0.025]), rep(min(density(result[, 'Sens_Yoden'])$y[density(result[, 'Sens_Yoden'])$y/max(density(result[, 'Sens_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'Sens_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'Sens_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
plot(density(result[, 'Spec_TL']), main = NA, xlab = 'Специфичность', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'Spec_TL'])$x[density(result[, 'Spec_TL'])$y/max(density(result[, 'Spec_TL'])$y)>=0.025]), rep(min(density(result[, 'Spec_TL'])$y[density(result[, 'Spec_TL'])$y/max(density(result[, 'Spec_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'Spec_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'Spec_Yoden'])$x[density(result[, 'Spec_Yoden'])$y/max(density(result[, 'Spec_Yoden'])$y)>=0.025]), rep(min(density(result[, 'Spec_Yoden'])$y[density(result[, 'Spec_Yoden'])$y/max(density(result[, 'Spec_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'Spec_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'Spec_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
plot(density(result[, 'Accuracy_TL']), main = NA, xlab = 'Точность', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'Accuracy_TL'])$x[density(result[, 'Accuracy_TL'])$y/max(density(result[, 'Accuracy_TL'])$y)>=0.025]), rep(min(density(result[, 'Accuracy_TL'])$y[density(result[, 'Accuracy_TL'])$y/max(density(result[, 'Accuracy_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'Accuracy_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'Accuracy_Yoden'])$x[density(result[, 'Accuracy_Yoden'])$y/max(density(result[, 'Accuracy_Yoden'])$y)>=0.025]), rep(min(density(result[, 'Accuracy_Yoden'])$y[density(result[, 'Accuracy_Yoden'])$y/max(density(result[, 'Accuracy_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'Accuracy_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'Accuracy_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
plot(density(result[, 'OR_TL']), main = NA, xlab = 'Отношение шансов', ylab = 'Плотность',
col = rgb(0,1,0.7))
lines(range(density(result[, 'OR_TL'])$x[density(result[, 'OR_TL'])$y/max(density(result[, 'OR_TL'])$y)>=0.025]), rep(min(density(result[, 'OR_TL'])$y[density(result[, 'OR_TL'])$y/max(density(result[, 'OR_TL'])$y)>=0.025]),2),
col = rgb(0,1,0.7), lty = 1, lwd = 2)
lines(density(result[, 'OR_Yoden']), col = rgb(1,1,0.5))
lines(range(density(result[, 'OR_Yoden'])$x[density(result[, 'OR_Yoden'])$y/max(density(result[, 'OR_Yoden'])$y)>=0.025]), rep(min(density(result[, 'OR_Yoden'])$y[density(result[, 'OR_Yoden'])$y/max(density(result[, 'OR_Yoden'])$y)>=0.025]),2),
col = rgb(1,1,0.5), lty = 2, lwd = 2)
abline(v=mean(result[, 'OR_TL']), col = rgb(0,1,0.7), lty = 2, lwd = 2)
abline(v=mean(result[, 'OR_Yoden']), col = rgb(1,1,0.5), lty = 2, lwd = 2)
Сравнение интересующих характеристик для двух моделей (среднее ± ст.отклонение).
df <- as.data.frame(cbind(res1, res2))
row.names(df) <- c('Доля Y=1','AUC','Порог (Yoden)','Чувствительность (Yoden)','Специфичность (Yoden)','Точность (Yoden)', 'Отношение шансов (Yoden)', 'Порог (TopLeft)','Чувствительнсть (TopLeft)','Специфичность (TopLeft)','Точность (TopLeft)','Отношение шансов (TopLeft)')
names(df) <- c('Модель #1','Модель #2')
df %>% kable(row.names = T) %>%
kable_styling(bootstrap_options = 'hover',full_width = F)
| Модель #1 | Модель #2 | |
|---|---|---|
| Доля Y=1 | 0.503 (0.05) | 0.5 (0.044) |
| AUC | 0.8 (0.046) | 0.772 (0.045) |
| Порог (Yoden) | 100.04 (1.405) | 99.997 (0.73) |
| Чувствительность (Yoden) | 0.805 (0.059) | 0.736 (0.114) |
| Специфичность (Yoden) | 0.808 (0.059) | 0.735 (0.115) |
| Точность (Yoden) | 0.806 (0.039) | 0.735 (0.039) |
| Отношение шансов (Yoden) | 22.941 (19.247) | 10.649 (6.586) |
| Порог (TopLeft) | 100.03 (1.187) | 99.998 (0.39) |
| Чувствительнсть (TopLeft) | 0.804 (0.054) | 0.731 (0.068) |
| Специфичность (TopLeft) | 0.808 (0.054) | 0.73 (0.069) |
| Точность (TopLeft) | 0.806 (0.039) | 0.73 (0.04) |
| Отношение шансов (TopLeft) | 22.258 (18.72) | 8.614 (4.245) |