2010-09-23 23 views
15

Ho un programma che ha bisogno di calcolare ripetutamente il percentile approssimativo (statistica dell'ordine) di un set di dati per rimuovere i valori anomali prima dell'ulteriore elaborazione. Attualmente sto facendo questo ordinando la matrice di valori e selezionando l'elemento appropriato; questo è fattibile, ma è un blip apprezzabile sui profili nonostante sia una parte piuttosto secondaria del programma.Algoritmo rapido per calcolare percentili per rimuovere i valori anomali

Maggiori informazioni:

  • L'insieme di dati contiene dell'ordine di un massimo di 100000 numeri in virgola mobile, e assunto come "ragionevolmente" distribuito - c'è improbabile che siano duplicati, né enormi picchi di densità nei pressi di particolare valori; e se per qualche strana ragione la distribuzione è dispari, va bene che un'approssimazione sia meno accurata dato che i dati probabilmente sono incasinati e ulteriori elaborazioni discutibili. Tuttavia, i dati non sono necessariamente distribuiti uniformemente o normalmente; è molto improbabile che sia degenerato.
  • Una soluzione approssimativa andrebbe bene, ma ho bisogno di capire come l' l'approssimazione introduce errore per assicurarsi che sia valido.
  • Poiché lo scopo è rimuovere i valori anomali, sto calcolando due percentuali sugli stessi dati in ogni momento: ad es. uno al 95% e uno al 5%.
  • L'app è in C# con bit di sollevamento pesante in C++; pseudocodice o una libreria preesistente in entrambi andrebbero bene.
  • Un modo completamente diverso di rimuovere i valori anomali andrebbe bene, purché sia ​​ragionevole.
  • Aggiornamento: Sembra che stia cercando un approssimativo selection algorithm.

Anche se questo è tutto fatto in un ciclo, i dati sono (leggermente) diverso ogni volta, quindi non è facile da riutilizzare una datastructure come è stato fatto for this question.

implementato la soluzione

Usando l'algoritmo di selezione wikipedia come suggerito da Gronim ridotto questa parte del run-time di circa un fattore 20.

Dal momento che non riuscivo a trovare un'implementazione C#, ecco quello che ho si avvicinò. È più veloce anche per piccoli input rispetto a Array.Sort; e a 1000 elementi è 25 volte più veloce.

public static double QuickSelect(double[] list, int k) { 
    return QuickSelect(list, k, 0, list.Length); 
} 
public static double QuickSelect(double[] list, int k, int startI, int endI) { 
    while (true) { 
     // Assume startI <= k < endI 
     int pivotI = (startI + endI)/2; //arbitrary, but good if sorted 
     int splitI = partition(list, startI, endI, pivotI); 
     if (k < splitI) 
      endI = splitI; 
     else if (k > splitI) 
      startI = splitI + 1; 
     else //if (k == splitI) 
      return list[k]; 
    } 
    //when this returns, all elements of list[i] <= list[k] iif i <= k 
} 
static int partition(double[] list, int startI, int endI, int pivotI) { 
    double pivotValue = list[pivotI]; 
    list[pivotI] = list[startI]; 
    list[startI] = pivotValue; 

    int storeI = startI + 1;//no need to store @ pivot item, it's good already. 
    //Invariant: startI < storeI <= endI 
    while (storeI < endI && list[storeI] <= pivotValue) ++storeI; //fast if sorted 
    //now storeI == endI || list[storeI] > pivotValue 
    //so elem @storeI is either irrelevant or too large. 
    for (int i = storeI + 1; i < endI; ++i) 
     if (list[i] <= pivotValue) { 
      list.swap_elems(i, storeI); 
      ++storeI; 
     } 
    int newPivotI = storeI - 1; 
    list[startI] = list[newPivotI]; 
    list[newPivotI] = pivotValue; 
    //now [startI, newPivotI] are <= to pivotValue && list[newPivotI] == pivotValue. 
    return newPivotI; 
} 
static void swap_elems(this double[] list, int i, int j) { 
    double tmp = list[i]; 
    list[i] = list[j]; 
    list[j] = tmp; 
} 

Performance Graph

Grazie, Gronim, per me che punta nella direzione giusta!

risposta

8

La soluzione istogramma di Henrik funzionerà. È inoltre possibile utilizzare un algoritmo di selezione per trovare in modo efficiente gli elementi k più grandi o più piccoli in un array di n elementi in O (n). Per usarlo per il 95 ° percentile imposta k = 0.05n e trova gli elementi k più grandi.

Riferimento:

http://en.wikipedia.org/wiki/Selection_algorithm#Selecting_k_smallest_or_largest_elements

+0

Giusto, questo è quello che stavo cercando: un algoritmo di selezione! –

3

Dividere l'intervallo tra il minimo e il massimo dei dati in (ad esempio) 1000 bin e calcolare un istogramma. Quindi crea somme parziali e vedi dove prima superano i 5000 o i 95000.

+0

Nice ... quicksort, e taglia il 5000 in alto e in basso. Senza conoscere la distribuzione, non sai come potresti fare meglio. – John

+0

L'ordinamento della benna è più appropriato per questo. – Brian

+1

Sembra eminentemente pratico, anche se non sempre efficace. Alcuni valori anomali estremi potrebbero davvero distorcere i cassonetti ... –

0

non sono un esperto, ma la mia memoria suggerisce:

  • per determinare i punti percentili esattamente è necessario selezionare e contare
  • prelievo di un campione dai dati e calcolando i valori percentili suona come un buon piano per approssimazione decente se è possibile ottenere un buon campione
  • se non, come suggerito da Henrik, è possibile evitare il completo sorta se fate i secchi e contarli
4

si potrebbe stimare i tuoi percentili solo da una parte del set di dati, come le prime migliaia di punti.

Il Glivenko–Cantelli theorem assicura che questa sarebbe una stima abbastanza buona, se si può ritenere che i propri punti di dati siano indipendenti.

+0

Sfortunatamente, i punti dati non sono indipendenti, sono ordinati per criteri esterni - ma potrei scorrere in ordine casuale. Non capisco come il teorema collegato mi permettesse praticamente di stimare i percentili - puoi fare un esempio, ad es. per la normale distribuzione? –

+0

@Eamon: il teorema collegato afferma semplicemente che la funzione di distribuzione empirica (che si utilizzerà implicitamente quando si calcolano i percentili in base ai dati) è una buona stima per la distribuzione reale. Non è necessario usarlo in realtà =) – Jens

+0

Ah, OK, capisco cosa intendi :-) –

1

Ci sono un paio di approcci di base che posso pensare. Il primo consiste nel calcolare l'intervallo (trovando i valori più alti e quelli più bassi), proiettare ciascun elemento in un percentile ((x - min)/intervallo) e eliminare quelli che valutano a valori inferiori a 0,05 o superiori a 0,95.

Il secondo è per calcolare la media e la deviazione standard. Un'intervallo di 2 deviazioni standard dalla media (in entrambe le direzioni) includerà il 95% di uno spazio campione distribuito normalmente, il che significa che i valori anomali si troveranno nei valori percentuali < 2,5 e> 97,5. Il calcolo della media di una serie è lineare, come lo è lo standard dev (radice quadrata della somma della differenza di ciascun elemento e della media). Quindi, sottraete 2 sigma dalla media e aggiungete 2 sigmas alla media, e avrete i limiti outlier.

Entrambe calcolano in un tempo approssimativamente lineare; il primo richiede due passaggi, il secondo ne richiede tre (una volta che hai i tuoi limiti devi ancora scartare i valori anomali). Poiché si tratta di un'operazione basata su elenchi, non penso che troverete nulla con complessità logaritmica o costante; eventuali ulteriori miglioramenti delle prestazioni richiederebbero l'ottimizzazione dell'iterazione e del calcolo o l'introduzione di errori eseguendo i calcoli su un sottocampione (come ogni terzo elemento).

+0

Il primo suggerimento non è quello di buttare fuori il 5 ° percentile esterno ma fare qualcosa in base ai valori estremi che è altamente instabile . Il secondo suggerimento fa presupporre che i dati siano distribuiti normalmente, cosa che in modo esplicito non è. –

4

Ho utilizzato per identificare i valori anomali calcolando il standard deviation. Tutto con una distanza maggiore di 2 (o 3) volte la deviazione standard dalla media è un valore anomalo. 2 volte = circa il 95%.

Dato che stai calcolando l'avio, è anche molto facile calcolare la deviazione standard molto velocemente.

È anche possibile utilizzare solo un sottoinsieme di dati per calcolare i numeri.

+2

I dati non sono distribuiti normalmente. –

6

According al suo creatore un SoftHeap può essere utilizzato per:

elaborazione esatta o approssimativa mediane e percentili in modo ottimale.E 'anche utile per l'ordinamento approssimativa ...

+0

+1 Hmm, sembra interessante! –

+0

@Eamon l'intera idea alla base del SoftHeap e le sue applicazioni sono davvero fantastiche. –

+0

@EugenConstantinDinca: Grazie per l'ottima idea! Esiste una reale implementazione di questo da qualche parte o la carta/wiki sono le uniche fonti? – Legend

1

Una buona risposta generale al problema sembra essere RANSAC. Dato un modello e alcuni dati rumorosi, l'algoritmo recupera in modo efficiente i parametri del modello.
Dovrai scegliere un modello semplice in grado di mappare i tuoi dati. Tutto liscio dovrebbe andare bene. Diciamo una miscela di pochi gaussiani. RANSAC imposterà i parametri del tuo modello e stimerà un insieme di inliner allo stesso tempo. Quindi buttare via tutto ciò che non si adatta al modello correttamente.

+0

Ho una serie di numeri - non un modello complesso - RANSAC sembra che sia lento e soggetto a errori e che per un caso così semplice esistono soluzioni migliori. –

0

Una serie di dati di 100k elementi richiede pochissimo tempo per ordinare, quindi suppongo che dovete fare questo più volte. Se il set di dati è lo stesso set appena aggiornato leggermente, è meglio costruire un albero (O(N log N)) e quindi rimuovere e aggiungere nuovi punti non appena entrati (O(K log N) dove K è il numero di punti modificati). In caso contrario, la soluzione di elemento più grande già menzionata fornisce O(N) per ciascun set di dati.

1

Si potrebbe filtrare 2 o 3 deviazioni standard anche se i dati non è distribuito normalmente; almeno, sarà fatto in modo coerente, che dovrebbe essere importante.

Come si rimuovono le valori anomali, il dev std cambierà, si potrebbe fare questo in un ciclo fino a quando il cambiamento di std dev è minimo. Che tu voglia o meno farlo dipende da come stai manipolando i dati in questo modo. Ci sono importanti riserve da parte di alcuni statistici per rimuovere i valori anomali. Ma alcuni rimuovono i valori anomali per dimostrare che i dati sono distribuiti in modo abbastanza normale.

+0

Se i dati si trovano per lo più negli estremi, cioè il contrario del normale, se lo si desidera, allora questo approccio può rimuovere grandi serie di dati. Non voglio davvero rimuovere più di una piccola parte dei dati, e preferibilmente solo quella quando questi sono valori anomali. Sto sopprimendo i valori anomali perché sono fonte di distrazione: sono solo ritagliati dalla visualizzazione, non dai dati effettivi. –

+0

Per definizione, solo una piccola parte dei dati può essere agli estremi. Per disuguaglianza di Chebyshev, solo 1/9 della tua distribuzione può essere superiore a 3 deviazioni standard; solo 1/16 possono essere 4 deviazioni. E quei limiti sono raggiunti solo nel caso degenere in cui la tua distribuzione è solo di due picchi. Quindi, calcolare la deviazione in O (N) è un modo valido ed efficiente per filtrare i valori anomali. – MSalters

+0

@MSalters: (si risponde a un commento di 3 anni): la disuguaglianza di chebyshev non è sufficientemente precisa per essere pratica. Per ritagliare almeno il 95% del set di dati avrei bisogno di fare 4,5 sigma; ma se i dati fossero normali, mostrerei il 99,999% dei dati, ben lontano dall'obiettivo. In altre parole, sarei stato ingrandito troppo lontano da un fattore 2,25, cioè mostrando un'area 5 volte superiore a quella necessaria, lasciando così piccoli bit interessanti. E se i dati sono più spinosi del normale, è anche peggio. Quindi, certo, questo potrebbe essere un minimo assoluto, ma non è una grande approssimazione. –

Problemi correlati