Come calcolare l'Area Under the Curve (AUC), o la statistica c, a mano


78

Sono interessato a calcolare manualmente l'area sotto la curva (AUC), o statistica c, per un modello di regressione logistica binaria.

Ad esempio, nel set di dati di convalida, ho il valore reale per la variabile dipendente, retention (1 = mantenuto; 0 = non mantenuto), nonché uno stato di conservazione previsto per ogni osservazione generata dalla mia analisi di regressione utilizzando un modello che era costruito usando il set di allenamento (questo andrà da 0 a 1).

Il mio pensiero iniziale era di identificare il numero "corretto" di classificazioni del modello e semplicemente dividere il numero di osservazioni "corrette" per il numero di osservazioni totali per calcolare la statistica c. Con "corretto", se lo stato di conservazione reale di un'osservazione = 1 e lo stato di conservazione previsto è> 0,5, questa è una classificazione "corretta". Inoltre, se lo stato di conservazione reale di un'osservazione = 0 e lo stato di conservazione previsto è <0,5, anche questa è una classificazione "corretta". Suppongo che si verificherebbe un "pareggio" quando il valore previsto = 0,5, ma quel fenomeno non si verifica nel mio set di dati di convalida. D'altra parte, le classificazioni "errate" sarebbero se lo stato di conservazione reale di un'osservazione = 1 e lo stato di conservazione previsto è <0. 5 o se lo stato di conservazione reale per un risultato = 0 e lo stato di conservazione previsto è> 0,5. Sono a conoscenza di TP, FP, FN, TN, ma non so come calcolare la statistica c data queste informazioni.

Risposte:


115

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)

Curva AUC

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")

Grafico a dispersione del punteggio di rischio rispetto allo stato di malattia reale.

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))

Grafico a dispersione del punteggio di rischio rispetto allo stato di malattia reale, con linee tra tutte le possibili coppie di osservazione.

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 Epipacchetto crea una bella curva ROC con varie statistiche (inclusa l'AUC) incorporate:

library(Epi)
ROC(testres, truestat) # also try adding plot="sp"

Curva ROC dal pacchetto Epi

Il pacchetto pROC

Mi piace anche il pROCpacchetto, dal momento che può lisciare la stima del ROC (e calcolare una stima AUC basata sul ROC livellato):

Curva ROC (non levigata e levigata) dal pacchetto pROC

(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 rmspacchetto di Harrell può calcolare varie statistiche di concordanza correlate usando la rcorr.cens()funzione. Nel C Indexsuo output è l'AUC:

> library(rms)
> rcorr.cens(testres,truestat)[1]
  C Index 
0.8931711

Il pacchetto caTools

Finalmente abbiamo il caToolspacchetto 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

Curva ROC dal pacchetto caTools

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. :)


8
Grazie, @Karl Ove Hufthammer, questa è la risposta più completa che io abbia mai ricevuto. Apprezzo in particolare la sezione "Parole finali". Lavoro eccellente! Grazie ancora!
Matt Reichenbach,

Grazie mille per questa risposta dettagliata. Sto lavorando con un set di dati in cui Epi :: ROC () v2.2.6 è convinto che l'AUC sia 1.62 (no, non è uno studio mentalista), ma secondo il ROC, credo molto di più nella 0,56 che il codice sopra riportato risulta in.
BurninLeo

32

Dai un'occhiata a questa domanda: comprensione della curva ROC

Ecco come costruire una curva ROC (da quella domanda):

Disegno della curva ROC

dato un set di dati elaborato dal tuo classificatore di classificazione

  • classifica esempi di test su punteggio decrescente
  • iniziare tra(0,0)
  • per ogni esempio (in ordine decrescente) x
    • se è positivo, sposta verso l'alto1 / posx1/pos
    • se è negativo, sposta destra1 / negx1/neg

dove e sono rispettivamente le frazioni di esempi positivi e negativi.negposneg

È possibile utilizzare questa idea per calcolare manualmente AUC ROC utilizzando il seguente algoritmo:

auc = 0.0
height = 0.0

for each training example x_i, y_i
  if y_i = 1.0:
    height = height + tpr
  else 
    auc = auc + height * fpr

return auc

Questa bella immagine gif animata dovrebbe illustrare questo processo in modo più chiaro

costruendo la curva


1
Grazie @Alexey Grigorev, questa è un'ottima immagine e probabilmente si rivelerà utile in futuro! +1
Matt Reichenbach

1
Potresti spiegare un po 'di "frazioni di esempi positivi e negativi", intendi il valore unitario più piccolo di due assi?
Allan Ruin,

1
@Allan Ruin: posqui indica il numero di dati positivi. Diciamo che hai 20 punti dati, in cui 11 punti sono 1. Quindi, quando si disegna il grafico, abbiamo un rettangolo 11x9 (altezza x larghezza). Alexey Grigorev ha ridimensionato, ma lascialo com'è, se vuoi. Ora, basta spostare 1 sul grafico ad ogni passaggio.
Catbuilts

5

Il post di Karl ha molte informazioni eccellenti. Ma non ho ancora visto negli ultimi 20 anni un esempio di una curva ROC che ha cambiato il modo di pensare di qualcuno in una buona direzione. L'unico valore di una curva ROC secondo la mia modesta opinione è che la sua area coincide con una probabilità di concordanza molto utile. La stessa curva ROC tenta il lettore di utilizzare i cutoff, che è una cattiva pratica statistica.

Per quanto riguarda il calcolo manuale dell'indice , tracciare un diagramma con sull'asse il predittore continuo o la probabilità prevista che sull'asse . Se si collega ogni punto con con ogni punto con , la proporzione delle linee che hanno una pendenza positiva è la probabilità di concordanza.Y = 0 , 1 x Y = 1 y Y = 0 Y = 1cY=0,1xY=1yY=0Y=1

Tutte le misure che hanno un denominatore di in questa impostazione sono regole di valutazione della precisione improprie e devono essere evitate. Ciò include proporzioni classificate correttamente, sensibilità e specificità.n

Per la funzione Hmiscpacchetto R rcorr.cens, stampare l'intero risultato per visualizzare ulteriori informazioni, in particolare un errore standard.


Grazie, Frank Harell, apprezzo la tua prospettiva. Uso semplicemente la statistica c come probabilità di concordanza, poiché non mi piacciono i cutoff. Grazie ancora!
Matt Reichenbach,

4

Ecco un'alternativa al modo naturale di calcolare l'AUC semplicemente usando la regola trapezoidale per ottenere l'area sotto la curva ROC.

L'AUC è uguale alla probabilità che un'osservazione positiva campionata casualmente abbia una probabilità prevista (di essere positiva) maggiore di un'osservazione negativa campionata casualmente. Puoi usarlo per calcolare l'AUC abbastanza facilmente in qualsiasi linguaggio di programmazione esaminando tutte le combinazioni a coppie di osservazioni positive e negative. È anche possibile campionare casualmente le osservazioni se la dimensione del campione era troppo grande. Se si desidera calcolare l'AUC utilizzando carta e penna, questo potrebbe non essere l'approccio migliore a meno che non si abbia un campione molto piccolo / molto tempo. Ad esempio in R:

n <- 100L

x1 <- rnorm(n, 2.0, 0.5)
x2 <- rnorm(n, -1.0, 2)
y <- rbinom(n, 1L, plogis(-0.4 + 0.5 * x1 + 0.1 * x2))

mod <- glm(y ~ x1 + x2, "binomial")

probs <- predict(mod, type = "response")

combinations <- expand.grid(positiveProbs = probs[y == 1L], 
        negativeProbs = probs[y == 0L])

mean(combinations$positiveProbs > combinations$negativeProbs)
[1] 0.628723

Possiamo verificare usando il pROCpacchetto:

library(pROC)
auc(y, probs)
Area under the curve: 0.6287

Utilizzo del campionamento casuale:

mean(sample(probs[y == 1L], 100000L, TRUE) > sample(probs[y == 0L], 100000L, TRUE))
[1] 0.62896

1
  1. Hai un vero valore per le osservazioni.
  2. Calcola la probabilità posteriore e poi classifica le osservazioni in base a questa probabilità.
  3. PN
    Sum of true ranks0.5PN(PN+1)PN(NPN)

1
@ user73455 ... 1) Sì, ho il vero valore per le osservazioni. 2) La probabilità posteriore è sinonimo di probabilità previste per ciascuna delle osservazioni? 3) Capito; tuttavia, che cos'è "Somma dei ranghi reali" e come si calcola questo valore? Forse un esempio potrebbe aiutarti a spiegare questa risposta in modo più approfondito? Grazie!
Matt Reichenbach,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.