Consiglierei il documento di Hanley & McNeil del 1982 " Il significato e l'uso dell'area sotto una curva caratteristica operativa del ricevitore (ROC) ".
Esempio
Hanno la seguente tabella dello stato della malattia e il risultato del test (corrispondente, ad esempio, al rischio stimato derivante da un modello logistico). Il primo numero a destra è il numero di pazienti con stato di malattia reale "normale" e il secondo numero è il numero di pazienti con stato di malattia reale "anormale":
(1) Decisamente normale: 33/3
(2) Probabilmente normale: 6/2
(3) Discutibile: 6/2
(4) Probabilmente anormale: 11/11
(5) Decisamente anormale: 2/33
Quindi ci sono in totale 58 pazienti "normali" e "51" anormali. Vediamo che quando il predittore è 1, "Sicuramente normale", il paziente è di solito normale (vero per 33 dei 36 pazienti) e quando è 5, "Sicuramente anormale" i pazienti sono di solito anormali (vero per 33 dei 35 pazienti), quindi il predittore ha un senso. Ma come dovremmo giudicare un paziente con un punteggio di 2, 3 o 4? Ciò che impostiamo il nostro limite per giudicare un paziente come anormale o normale determina la sensibilità e la specificità del test risultante.
Sensibilità e specificità
Siamo in grado di calcolare la sensibilità stimata e la specificità per diversi valori di cutoff. (Scriverò 'sensibilità' e 'specificità' d'ora in poi, lasciando implicita la natura stimata dei valori.)
Se scegliamo il nostro cutoff in modo da classificare tutti i pazienti come anormali, indipendentemente da ciò che dicono i loro risultati del test (cioè, scegliamo il cutoff 1+), avremo una sensibilità di 51/51 = 1. La specificità sarà 0 / 58 = 0. Non suona così bene.
OK, quindi scegliamo un taglio meno rigoroso. Classifichiamo i pazienti come anormali solo se hanno un risultato del test di 2 o superiore. Ci mancano quindi 3 pazienti anormali e abbiamo una sensibilità di 48/51 = 0,94. Ma abbiamo una specificità molto maggiore, di 33/58 = 0,57.
Ora possiamo continuare questo, scegliendo vari cutoff (3, 4, 5,> 5). (Nell'ultimo caso, non classificheremo nessun paziente come anormale, anche se hanno il punteggio di test più alto possibile di 5.)
La curva ROC
Se lo facciamo per tutti i possibili tagli, e tracciamo la sensibilità contro 1 meno la specificità, otteniamo la curva ROC. Possiamo usare il seguente codice R:
# Data
norm = rep(1:5, times=c(33,6,6,11,2))
abnorm = rep(1:5, times=c(3,2,2,11,33))
testres = c(abnorm,norm)
truestat = c(rep(1,length(abnorm)), rep(0,length(norm)))
# Summary table (Table I in the paper)
( tab=as.matrix(table(truestat, testres)) )
L'output è:
testres
truestat 1 2 3 4 5
0 33 6 6 11 2
1 3 2 2 11 33
Possiamo calcolare varie statistiche:
( tot=colSums(tab) ) # Number of patients w/ each test result
( truepos=unname(rev(cumsum(rev(tab[2,])))) ) # Number of true positives
( falsepos=unname(rev(cumsum(rev(tab[1,])))) ) # Number of false positives
( totpos=sum(tab[2,]) ) # The total number of positives (one number)
( totneg=sum(tab[1,]) ) # The total number of negatives (one number)
(sens=truepos/totpos) # Sensitivity (fraction true positives)
(omspec=falsepos/totneg) # 1 − specificity (false positives)
sens=c(sens,0); omspec=c(omspec,0) # Numbers when we classify all as normal
E usando questo, possiamo tracciare la curva (stimata) del ROC:
plot(omspec, sens, type="b", xlim=c(0,1), ylim=c(0,1), lwd=2,
xlab="1 − specificity", ylab="Sensitivity") # perhaps with xaxs="i"
grid()
abline(0,1, col="red", lty=2)
Calcolo manuale dell'AUC
Possiamo calcolare facilmente l'area sotto la curva ROC, usando la formula per l'area di un trapezio:
height = (sens[-1]+sens[-length(sens)])/2
width = -diff(omspec) # = diff(rev(omspec))
sum(height*width)
Il risultato è 0,8931711.
Una misura di concordanza
L'AUC può anche essere vista come una misura di concordanza. Se prendiamo tutte le possibili coppie di pazienti in cui uno è normale e l'altro è anormale, possiamo calcolare con quale frequenza è quello anormale che ha il risultato del test più alto (dall'aspetto più anormale) (se hanno lo stesso valore, noi considera questo come "mezza vittoria"):
o = outer(abnorm, norm, "-")
mean((o>0) + .5*(o==0))
La risposta è di nuovo 0,8931711, l'area sotto la curva ROC. Questo sarà sempre il caso.
Una visione grafica della concordanza
Come sottolineato da Harrell nella sua risposta, anche questa ha un'interpretazione grafica. Tracciamo il punteggio del test (stima del rischio) sull'asse y e il vero stato della malattia sull'asse x (qui con alcuni jitter, per mostrare i punti sovrapposti):
plot(jitter(truestat,.2), jitter(testres,.8), las=1,
xlab="True disease status", ylab="Test score")
Tracciamo ora una linea tra ciascun punto a sinistra (un paziente "normale") e ogni punto a destra (un paziente "anormale"). La proporzione di linee con una pendenza positiva (cioè la proporzione di coppie concordanti ) è l'indice di concordanza (le linee piatte contano come "concordanza del 50%").
È un po 'difficile visualizzare le linee effettive per questo esempio, a causa del numero di legami (punteggio di rischio uguale), ma con un po' di jitter e trasparenza possiamo ottenere una trama ragionevole:
d = cbind(x_norm=0, x_abnorm=1, expand.grid(y_norm=norm, y_abnorm=abnorm))
library(ggplot2)
ggplot(d, aes(x=x_norm, xend=x_abnorm, y=y_norm, yend=y_abnorm)) +
geom_segment(colour="#ff000006",
position=position_jitter(width=0, height=.1)) +
xlab("True disease status") + ylab("Test\nscore") +
theme_light() + theme(axis.title.y=element_text(angle=0))
Vediamo che la maggior parte delle linee si inclina verso l'alto, quindi l'indice di concordanza sarà alto. Vediamo anche il contributo all'indice di ciascun tipo di coppia di osservazioni. La maggior parte proviene da pazienti normali con un punteggio di rischio pari a 1 associato a pazienti anormali con un punteggio di rischio pari a 5 (1-5 coppie), ma in buona parte deriva anche da 1–4 coppie e 4–5 coppie. Ed è molto facile calcolare l'indice di concordanza reale in base alla definizione della pendenza:
d = transform(d, slope=(y_norm-y_abnorm)/(x_norm-x_abnorm))
mean((d$slope > 0) + .5*(d$slope==0))
La risposta è di nuovo 0,8931711, ovvero l'AUC.
Il test Wilcoxon – Mann – Whitney
Esiste una stretta connessione tra la misura di concordanza e il test di Wilcoxon – Mann – Whitney. In realtà, quest'ultimo verifica se la probabilità di concordanza (cioè che è il paziente anormale in una coppia casuale normale / anormale che avrà il risultato del test più "anormale") è esattamente 0,5. E la sua statistica test è solo una semplice trasformazione della probabilità concordata stimata:
> ( wi = wilcox.test(abnorm,norm) )
Wilcoxon rank sum test with continuity correction
data: abnorm and norm
W = 2642, p-value = 1.944e-13
alternative hypothesis: true location shift is not equal to 0
La statistica test ( W = 2642
) conta il numero di coppie concordanti. Se lo dividiamo per il numero di coppie possibili, otteniamo un numero familiare:
w = wi$statistic
w/(length(abnorm)*length(norm))
Sì, è 0,8931711, l'area sotto la curva ROC.
Modi più semplici per calcolare l'AUC (in R)
Ma rendiamo la vita più facile per noi stessi. Esistono vari pacchetti che calcolano automaticamente l'AUC per noi.
Il pacchetto Epi
Il Epi
pacchetto crea una bella curva ROC con varie statistiche (inclusa l'AUC) incorporate:
library(Epi)
ROC(testres, truestat) # also try adding plot="sp"
Il pacchetto pROC
Mi piace anche il pROC
pacchetto, dal momento che può lisciare la stima del ROC (e calcolare una stima AUC basata sul ROC livellato):
(La linea rossa è il ROC originale e la linea nera è il ROC attenuato. Nota anche il rapporto di aspetto 1: 1 predefinito. Ha senso usarlo, poiché sia la sensibilità che la specificità hanno un intervallo 0-1.)
L'AUC stimato dal ROC livellato è 0,9107, simile, ma leggermente più grande, dell'AUC dal ROC non lisciato (se si osserva la figura, si può facilmente capire perché è più grande). (Anche se abbiamo davvero pochi valori di risultati del test distinti possibili per calcolare un AUC regolare).
Il pacchetto rms
Il rms
pacchetto di Harrell può calcolare varie statistiche di concordanza correlate usando la rcorr.cens()
funzione. Nel C Index
suo output è l'AUC:
> library(rms)
> rcorr.cens(testres,truestat)[1]
C Index
0.8931711
Il pacchetto caTools
Finalmente abbiamo il caTools
pacchetto e la sua colAUC()
funzione. Ha alcuni vantaggi rispetto ad altri pacchetti (principalmente velocità e capacità di lavorare con dati multidimensionali - vedi ?colAUC
) che a volte possono essere utili. Ma ovviamente dà la stessa risposta che abbiamo calcolato più e più volte:
library(caTools)
colAUC(testres, truestat, plotROC=TRUE)
[,1]
0 vs. 1 0.8931711
Parole finali
Molte persone sembrano pensare che l'AUC ci dica quanto è buono un test. E alcune persone pensano che l'AUC sia la probabilità che il test classifichi correttamente un paziente. E ' non è . Come puoi vedere dall'esempio e dai calcoli precedenti, l'AUC ci dice qualcosa su una famiglia di test, un test per ogni possibile cutoff.
E l'AUC è calcolata sulla base di valori soglia che non si utilizzerebbero mai in pratica. Perché dovremmo preoccuparci della sensibilità e della specificità dei valori di cut-off "insensati"? Tuttavia, è su questo che l'AUC si basa (parzialmente). (Naturalmente, se l'AUC è molto vicino a 1, quasi ogni possibile test avrà un grande potere discriminatorio e saremmo tutti molto felici.)
L'interpretazione di coppia 'normale-anormale casuale' dell'AUC è buona (e può essere estesa, ad esempio, ai modelli di sopravvivenza, in cui vediamo se è la persona con il più alto rischio (relativo) che muore prima). Ma uno non lo userebbe mai in pratica. È un caso raro in cui uno sa di avere una persona sana e una persona malata, non sa quale persona è quella malata e deve decidere quale di loro trattare. (In ogni caso, la decisione è facile; trattare quella con il rischio più alto stimato.)
Quindi penso che studiare l'attuale curva ROC sarà più utile che guardare semplicemente la misura sommaria dell'AUC. E se usi il ROC insieme a (stime dei) costi di falsi positivi e falsi negativi, insieme ai tassi di base di ciò che stai studiando, puoi ottenere da qualche parte.
Si noti inoltre che l'AUC misura solo la discriminazione , non la calibrazione. Cioè, misura se è possibile discriminare tra due persone (una malata e una sana), in base al punteggio di rischio. Per questo, si guarda solo relativi valori di rischio (o classifica, se si vuole, cfr l'interpretazione test di Wilcoxon-Mann-Whitney), non quelli assoluti, che si dovrebbe essere interessato. Ad esempio, se si divide ogni rischio stima dal tuo modello logistico di 2, otterrai esattamente la stessa AUC (e ROC).
Quando si valuta un modello di rischio, anche la calibrazione è molto importante. Per esaminare questo, esaminerai tutti i pazienti con un punteggio di rischio di circa, ad esempio 0,7, e vedrai se circa il 70% di questi era effettivamente malato. Fatelo per ogni possibile punteggio di rischio (possibilmente usando una sorta di smoothing / regressione locale). Traccia i risultati e otterrai una misura grafica della calibrazione .
Se avere un modello con sia buona taratura e buona la discriminazione, poi si inizia ad avere buon modello. :)