2012-02-06 20 views
11

C'è un flag di debugging condizionale che mi manca da Matlab: dbstop if infnandescribed here. Se impostato, questa condizione interromperà l'esecuzione del codice quando viene rilevato un Inf o NaN (IIRC, Matlab non ha NA).Come forzare un errore se si incontrano valori non finiti (NA, NaN o Inf)

Come posso ottenere questo risultato in R in un modo più efficiente rispetto al test di tutti gli oggetti dopo ogni operazione di assegnazione?

Al momento, gli unici modi che vedo per farlo sono via hack come la seguente:

  1. inserire manualmente un test, dopo tutti i luoghi dove si possono incontrare questi valori (ad esempio, una divisione, dove la divisione per 0 potrebbe verificarsi). Il test sarebbe quello di utilizzare is.finite(), described in this Q & A, su ogni elemento.
  2. Utilizzare body() per modificare il codice per chiamare una funzione separata, dopo ogni operazione o eventualmente solo ogni assegnazione, che verifica tutti gli oggetti (e possibilmente tutti gli oggetti in tutti gli ambienti).
  3. Modificare il codice sorgente di R (?!?)
  4. Tentativo di utilizzare tracemem per identificare le variabili che sono state modificate e controllare solo queste per valori errati.
  5. (Nuovo - vedere nota 2) Utilizzare un tipo di gestori di chiamata/callback per richiamare una funzione di test.

La prima opzione è ciò che sto facendo al momento. Questo è noioso, perché non posso garantire di aver controllato tutto. La seconda opzione testerà tutto, anche se un oggetto non è stato aggiornato. Questa è una grande perdita di tempo. La terza opzione comporterebbe la modifica di assegnamenti di NA, NaN e valori infiniti (+/- Inf), in modo che venga generato un errore. Sembra che sia meglio lasciarlo a R Core. La quarta opzione è come la seconda: mi occorrerebbe una chiamata a una funzione separata che elenca tutte le posizioni di memoria, solo per identificare quelli che sono cambiati e quindi controllare i valori; Non sono nemmeno sicuro che funzionerà per tutti gli oggetti, poiché un programma potrebbe eseguire una modifica sul posto, il che sembra non richiamare la funzione duplicate.

C'è un approccio migliore che mi manca? Forse uno strumento intelligente di Mark Bravington, Luke Tierney o qualcosa di relativamente semplice - qualcosa di simile a un parametro options() o un flag durante la compilazione di R?

Esempio di codice Ecco un esempio di codice molto semplice da testare, che incorpora la funzione addTaskCallback proposta da Josh O'Brien. Il codice non viene interrotto, ma si verifica un errore nel primo scenario, mentre nel secondo caso non si verifica alcun errore (ad esempio, badDiv(0,0,FALSE) non si interrompe). Sto ancora indagando sui callback, visto che sembra promettente.

badDiv <- function(x, y, flag){ 
    z = x/y 
    if(flag == TRUE){ 
     return(z) 
    } else { 
     return(FALSE) 
    } 
} 

addTaskCallback(stopOnNaNs) 
badDiv(0, 0, TRUE) 

addTaskCallback(stopOnNaNs) 
badDiv(0, 0, FALSE) 

Nota 1. Sarei soddisfatto con una soluzione per operazioni standard R, anche se molti miei calcoli coinvolgono oggetti usati tramite data.table o bigmemory (cioè memoria su disco matrici mappati). Questi sembrano avere comportamenti di memoria un po 'diversi rispetto alle operazioni standard matrix e data.frame.

Nota 2. L'idea dei callback sembra un po 'più promettente, in quanto ciò non richiede che io scriva funzioni che mutino il codice R, ad es. tramite l'idea body().

Nota 3.Non so se ci sia o meno un modo semplice per verificare la presenza di valori non finiti, ad es. meta informazioni sugli oggetti che indicizza dove NAs, Infs, ecc. sono memorizzati nell'oggetto o se questi sono memorizzati sul posto. Finora, ho provato il pacchetto inspect di Simon Urbanek e non ho trovato un modo per indovinare la presenza di valori non numerici.

Follow-up: Simon Urbanek ha sottolineato in un commento che tali informazioni non sono disponibili come meta per gli oggetti.

Nota 4. Sto ancora testando le idee presentate. Inoltre, come suggerito da Simon, il test per la presenza di valori non finiti dovrebbe essere il più veloce in C/C++; che dovrebbe superare anche il codice R compilato, ma sono aperto a qualsiasi cosa. Per set di dati di grandi dimensioni, ad es. dell'ordine di 10-50 GB, questo dovrebbe essere un notevole risparmio rispetto alla copia dei dati. Si possono ottenere ulteriori miglioramenti tramite l'uso di più core, ma questo è un po 'più avanzato.

+0

Alcune delle funzioni primitive hanno questa funzionalità built-in, cioè, restituiscono un errore o di un avviso se in dotazione o consegnare un non -punto finito Prendi, 'sin (Inf)' per esempio. Forse è qualcosa che potresti esplorare. –

+0

Beh, non è sempre il caso che un Inf o un NaN * debbano * interrompere la tua funzione/codice (NA è un caso a parte perché è deliberatamente usato sempre come "riempitivo" o "marcatore"). Eseguo spesso alcune operazioni che producono alcuni valori Inf, diciamo nelle regioni a basso segnale di alcune matrici. Sospetto che otterrai informazioni migliori usando 'is.infinite' e/o' is.nan' su variabili sospette comunque. –

+0

@CarlWitthoft Nello scenario di codice + dati su cui sto lavorando attualmente, i valori dei problemi sono precisamente quelli tre: NA, NaN e Infs. In altri casi, ho sicuramente bisogno di NA, ma non oggi. :) Ho bisogno del codice per abortire (è piuttosto costoso dal punto di vista computazionale, b/c del volume di dati) non appena questi si verificano. Quindi, la ragione per cui mi piacerebbe davvero innescare un errore (o almeno un avvertimento). – Iterator

risposta

7

Temo che non ci sia tale scorciatoia. In teoria su UNIX c'è SIGFPE che si potrebbe trappola, ma in pratica

  1. non esiste un modo standard per consentire operazioni di FP per intrappolare (anche C99 non include una disposizione per questo) - è altamente system-specifc (ad es. feenableexcept su Linux, fp_enable_all su AIX ecc.) o richiede l'uso dell'assembler per la CPU di destinazione
  2. Le operazioni FP sono oggi eseguite spesso in unità vettoriali come SSE, quindi non si può nemmeno essere sicuri che FPU sia coinvolto e
  3. R intercetta alcune operazioni su cose come NaN s, NA s e le gestisce separatamente in modo che non ce la fanno a t he FP code

Detto questo, potresti hackerarti una R che prenderà alcune eccezioni per la tua piattaforma e CPU se ci hai provato abbastanza (disabilita SSE ecc.). Non è qualcosa che considereremmo costruire in R, ma per uno scopo speciale potrebbe essere fattibile.

Tuttavia, non è ancora possibile rilevare le operazioni NaN/NA a meno che non si modifichi il codice interno R. Inoltre, è necessario verificare ogni singolo pacchetto in uso poiché potrebbero utilizzare le operazioni FP nel proprio codice C e potrebbe anche gestire separatamente NA/NaN.

Se si è preoccupati solo di cose come la divisione per zero o over/underflow, quanto sopra funzionerà ed è probabilmente il più vicino a qualcosa come una soluzione.

Solo il controllo dei risultati potrebbe non essere molto affidabile, perché non si sa se un risultato si basa su un calcolo intermedio NaN che ha modificato un valore aggregato che potrebbe non essere necessario anche NaN. Se sei disposto a scartare questo caso, puoi semplicemente camminare ricorsivamente attraverso gli oggetti risultato o lo spazio di lavoro. Questo non dovrebbe essere estremamente inefficiente, perché devi solo preoccuparti di REALSXP e non di qualsiasi altra cosa (a meno che non ti piaccia lo NA s - allora avresti più lavoro).


Questo è un esempio di codice che potrebbe essere utilizzato per attraversare R oggetto ricorsivo:

static int do_isFinite(SEXP x) { 
    /* recurse into generic vectors (lists) */ 
    if (TYPEOF(x) == VECSXP) { 
     int n = LENGTH(x); 
     for (int i = 0; i < n; i++) 
      if (!do_isFinite(VECTOR_ELT(x, i))) return 0; 
    } 
    /* recurse into pairlists */ 
    if (TYPEOF(x) == LISTSXP) { 
     while (x != R_NilValue) { 
      if (!do_isFinite(CAR(x))) return 0; 
      x = CDR(x); 
     } 
     return 1; 
    } 
    /* I wouldn't bother with attributes except for S4 
     where attributes are slots */ 
    if (IS_S4_OBJECT(x) && !do_isFinite(ATTRIB(x))) return 0; 
    /* check reals */ 
    if (TYPEOF(x) == REALSXP) { 
     int n = LENGTH(x); 
     double *d = REAL(x); 
     for (int i = 0; i < n; i++) if (!R_finite(d[i])) return 0; 
    } 
    return 1; 
} 

SEXP isFinite(SEXP x) { return ScalarLogical(do_isFinite(x)); } 

# in R: .Call("isFinite", x) 
+0

Accidenti, stavo aspettando che le nuvole si separassero, che gli angeli cantassero e che tu pubblicassi. Una volta ho letto "I fear ...", ho pensato "Oh sì, aspetta che Simon si presenti, questo tizio è così sbagliato ... vediamo chi è questo ..." :) Per quanto riguarda il motivo per cui non pubblico su R-Devel - R-Core è spaventoso. :) – Iterator

+0

Più seriamente, tuttavia, ho cercato callback, condition conditioner e il pacchetto 'inspect'. 1: È il caso che la struttura interna dell'oggetto non rivelerà se ci sono o meno Infs o NAs, per FP? Cioè c'è qualche meta-informazione sulla presenza/posizione di valori non finiti? 2: Se esistono tali informazioni, potrei usare i gestori di chiamata per richiamare una chiamata per verificare i valori errati dopo l'esecuzione di ogni istruzione? – Iterator

+0

Ti sembro spaventoso?;) Mi dispiace deluderti - IMHO 'SIGFPE' è davvero la strada da percorrere (sospetto che sia quello che usa Matlab) ma la mancanza di standard è davvero frustrante (e Matlab non ha bisogno di NA per casi speciali). –

7

L'idea delineato di seguito (e la sua implementazione) è molto imperfetta.Sono riluttante a suggerirlo, ma: (a) Penso che sia interessante, anche in tutta la sua bruttezza; e (b) posso pensare a situazioni in cui sarebbe utile. Dato che sembra che tu stia inserendo manualmente un controllo dopo ogni computazione, spero che la tua situazione sia una di quelle.

Il mio è un hack in due passaggi. Innanzitutto, definisco una funzione che è progettata per rilevare NaN s in diversi tipi di oggetto che potrebbero essere restituiti dai calcoli. Quindi, utilizzando addTaskCallback() per chiamare la funzione su .Last.value dopo che ciascuna attività/calcolo di livello superiore è stata completata. Quando trova uno NaN in uno di questi valori restituiti, genera un errore, che è possibile utilizzare per evitare ulteriori calcoli.

Tra i suoi difetti:

  • Se fate qualcosa come impostazione stop(error = recover), è difficile dire dove è stato attivato l'errore, dal momento che l'errore è sempre gettato dall'interno di stopOnNaNs().

  • Quando genera un errore, stopOnNaNs() viene terminato prima che possa restituire TRUE. Di conseguenza, viene rimosso dall'elenco delle attività e sarà necessario reimpostare con addTaskCallback(stopOnNaNs) se si desidera utilizzarlo nuovamente. (Vedi lo 'Arguments' section of ?addTaskCallback per maggiori dettagli).

Senza ulteriori indugi, ecco che è:


# Sketch of a function that tests for NaNs in several types of objects 
nanDetector <- function(X) { 
    # To examine data frames 
    if(is.data.frame(X)) { 
     return(any(unlist(sapply(X, is.nan)))) 
    } 
    # To examine vectors, matrices, or arrays 
    if(is.numeric(X)) { 
     return(any(is.nan(X))) 
    } 
    # To examine lists, including nested lists 
    if(is.list(X)) { 
     return(any(rapply(X, is.nan))) 
    } 
    return(FALSE) 
} 

# Set up the taskCallback 
stopOnNaNs <- function(...) { 
    if(nanDetector(.Last.value)) {stop("NaNs detected!\n")} 
    return(TRUE) 
} 
addTaskCallback(stopOnNaNs) 


# Try it out 
j <- 1:00 
y <- rnorm(99) 
l <- list(a=1:4, b=list(j=1:4, k=NaN)) 
# Error in function (...) : NaNs detected! 

# Subsequent time consuming code that could be avoided if the 
# error thrown above is used to stop its evaluation. 
+0

Hot diggity dog, questo ha molte idee interessanti lungo le linee stavo iniziando a prendere in considerazione, vale a dire l'uso di callback. Se funzionerà o meno nel mio codice, dovrò vedere, ma è comunque istruttivo. Grazie! – Iterator

+1

FWIW sarà davvero inefficiente eseguire i controlli nel codice R - è banale farlo in C poiché è essenzialmente solo "if (TYPEOF (x) == REALSXP) {double * d = REAL (x); int n = LENGTH (x); for (int i = 0; i

+0

@SimonUrbanek Grazie per la guida C - che dovrebbe essere certamente veloce. Questo approccio è facile da sviluppare da utilizzare tramite, ad esempio, il pacchetto 'inline'? O posso desiderare una stella che possa apparire in R? :) (Immagino sia una domanda su R-devel. ;-)) – Iterator