Come calcolare in modo efficiente una deviazione standard corrente?


87

Ho una serie di elenchi di numeri, ad esempio:

[0] (0.01, 0.01, 0.02, 0.04, 0.03)
[1] (0.00, 0.02, 0.02, 0.03, 0.02)
[2] (0.01, 0.02, 0.02, 0.03, 0.02)
     ...
[n] (0.01, 0.00, 0.01, 0.05, 0.03)

Quello che vorrei fare è calcolare in modo efficiente la media e la deviazione standard in ogni indice di un elenco, su tutti gli elementi dell'array.

Per fare ciò, ho fatto un ciclo attraverso l'array e ho sommato il valore in un dato indice di un elenco. Alla fine, divido ciascun valore nella mia "lista delle medie" per n(sto lavorando con una popolazione, non con un campione della popolazione).

Per eseguire la deviazione standard, eseguo nuovamente il ciclo, ora che ho calcolato la media.

Vorrei evitare di passare attraverso l'array due volte, una volta per la media e poi una per la SD (dopo che ho una media).

Esiste un metodo efficiente per calcolare entrambi i valori, passando solo una volta attraverso l'array? Qualsiasi codice in un linguaggio interpretato (ad es. Perl o Python) o pseudocodice va bene.



Grazie, controllerò quell'algoritmo. Sembra quello che mi serve.
Alex Reynolds,

Grazie per avermi indicato la risposta giusta, dmckee. Vorrei darti il ​​segno di spunta "risposta migliore", se desideri dedicare un momento per aggiungere la tua risposta di seguito (se desideri i punti).
Alex Reynolds

1
Inoltre, ci sono diversi esempi su rosettacode.org/wiki/Standard_Deviation
glenn jackman,

1
Wikipedia ha un'implementazione Python en.wikipedia.org/wiki/…
Hamish Grubijan

Risposte:


116

La risposta è usare l'algoritmo di Welford, che è molto chiaramente definito dopo i "metodi ingenui" in:

È numericamente più stabile rispetto alla semplice somma di quadrati in due passaggi o online suggerita in altre risposte. La stabilità conta davvero solo quando si hanno molti valori vicini tra loro poiché portano a ciò che è noto come " cancellazione catastrofica " nella letteratura in virgola mobile.

Potresti anche rispolverare la differenza tra la divisione per il numero di campioni (N) e N-1 nel calcolo della varianza (deviazione al quadrato). La divisione per N-1 porta a una stima imparziale della varianza dal campione, mentre la divisione per N in media sottostima la varianza (perché non tiene conto della varianza tra la media campionaria e la media vera).

Ho scritto due post sul blog sull'argomento che vanno in maggiori dettagli, incluso come eliminare i valori precedenti online:

Puoi anche dare un'occhiata al mio implementare Java; javadoc, source e unit test sono tutti online:


1
+1, per aver cura di eliminare i valori dall'algoritmo di Welford
Svisstack

3
Bella risposta, +1 per ricordare al lettore la differenza tra una popolazione stddev e un campione stddev.
Assad Ebrahim

Dopo essere tornato su questa domanda dopo tutti questi anni, volevo solo dire una parola di ringraziamento per aver dedicato del tempo per fornire un'ottima risposta.
Alex Reynolds

76

La risposta di base è accumulare la somma di x (chiamalo 'sum_x1') e x 2 (chiamalo 'sum_x2') mentre procedi. Il valore della deviazione standard è quindi:

stdev = sqrt((sum_x2 / n) - (mean * mean)) 

dove

mean = sum_x / n

Questa è la deviazione standard del campione; ottieni la deviazione standard della popolazione usando "n" invece di "n - 1" come divisore.

Potrebbe essere necessario preoccuparsi della stabilità numerica di prendere la differenza tra due grandi numeri se si ha a che fare con campioni di grandi dimensioni. Vai ai riferimenti esterni in altre risposte (Wikipedia, ecc.) Per ulteriori informazioni.


Questo è quello che stavo per suggerire. È il modo migliore e più veloce, supponendo che gli errori di precisione non siano un problema.
Ray Hidayat

2
Ho deciso di utilizzare l'algoritmo di Welford poiché funziona in modo più affidabile con lo stesso sovraccarico di calcolo.
Alex Reynolds

2
Questa è una versione semplificata della risposta e può fornire risultati non reali a seconda dell'input (cioè, quando sum_x2 <sum_x1 * sum_x1). Per garantire un risultato reale valido, vai con `sd = sqrt (((n * sum_x2) - (sum_x1 * sum_x1)) / (n * (n - 1)))
Dan Tao

2
@ Dan sottolinea un problema valido: la formula sopra si rompe per x> 1 perché finisci per prendere lo sqrt di un numero negativo. L'approccio di Knuth è: sqrt ((sum_x2 / n) - (mean * mean)) dove mean = (sum_x / n).
G__

1
@UriLoya - non hai detto nulla su come stai calcolando i valori. Tuttavia, se si utilizza intin C per memorizzare la somma dei quadrati, si verificano problemi di overflow con i valori elencati.
Jonathan Leffler

38

Ecco una traduzione letterale in puro Python dell'implementazione dell'algoritmo di Welford da http://www.johndcook.com/standard_deviation.html :

https://github.com/liyanage/python-modules/blob/master/running_stats.py

import math

class RunningStats:

    def __init__(self):
        self.n = 0
        self.old_m = 0
        self.new_m = 0
        self.old_s = 0
        self.new_s = 0

    def clear(self):
        self.n = 0

    def push(self, x):
        self.n += 1

        if self.n == 1:
            self.old_m = self.new_m = x
            self.old_s = 0
        else:
            self.new_m = self.old_m + (x - self.old_m) / self.n
            self.new_s = self.old_s + (x - self.old_m) * (x - self.new_m)

            self.old_m = self.new_m
            self.old_s = self.new_s

    def mean(self):
        return self.new_m if self.n else 0.0

    def variance(self):
        return self.new_s / (self.n - 1) if self.n > 1 else 0.0

    def standard_deviation(self):
        return math.sqrt(self.variance())

Utilizzo:

rs = RunningStats()
rs.push(17.0)
rs.push(19.0)
rs.push(24.0)

mean = rs.mean()
variance = rs.variance()
stdev = rs.standard_deviation()

print(f'Mean: {mean}, Variance: {variance}, Std. Dev.: {stdev}')

9
Questa dovrebbe essere la risposta accettata in quanto è l'unica che è sia corretta e mostra l'algoritmo, con riferimento a Knuth.
Johan Lundberg

26

Forse non quello che stavi chiedendo, ma ... Se usi un array numpy, farà il lavoro per te, in modo efficiente:

from numpy import array

nums = array(((0.01, 0.01, 0.02, 0.04, 0.03),
              (0.00, 0.02, 0.02, 0.03, 0.02),
              (0.01, 0.02, 0.02, 0.03, 0.02),
              (0.01, 0.00, 0.01, 0.05, 0.03)))

print nums.std(axis=1)
# [ 0.0116619   0.00979796  0.00632456  0.01788854]

print nums.mean(axis=1)
# [ 0.022  0.018  0.02   0.02 ]

A proposito, ci sono alcune discussioni interessanti in questo post del blog e commenti sui metodi one-pass per calcolare i mezzi e le varianze:


14

Il modulo runstats di Python serve proprio per questo genere di cose. Installa runstats da PyPI:

pip install runstats

I riepiloghi di Runstats possono produrre media, varianza, deviazione standard, asimmetria e curtosi in un singolo passaggio di dati. Possiamo usarlo per creare la tua versione "funzionante".

from runstats import Statistics

stats = [Statistics() for num in range(len(data[0]))]

for row in data:

    for index, val in enumerate(row):
        stats[index].push(val)

    for index, stat in enumerate(stats):
        print 'Index', index, 'mean:', stat.mean()
        print 'Index', index, 'standard deviation:', stat.stddev()

I riepiloghi statistici si basano sul metodo Knuth e Welford per il calcolo della deviazione standard in un passaggio, come descritto in Art of Computer Programming, Vol 2, p. 232, 3a edizione. Il vantaggio di questo è risultati numericamente stabili e accurati.

Disclaimer: sono l'autore del modulo runstats di Python.


Bel modulo. Sarebbe interessante se esistesse Statisticsun .popmetodo per calcolare anche le statistiche a rotazione.
Gustavo Bezerra

@GustavoBezerra runstatsnon mantiene un elenco interno di valori, quindi non sono sicuro che sia possibile. Ma le richieste pull sono benvenute.
GrantJ

8

Statistics :: Descriptive è un modulo Perl molto decente per questi tipi di calcoli:

#!/usr/bin/perl

use strict; use warnings;

use Statistics::Descriptive qw( :all );

my $data = [
    [ 0.01, 0.01, 0.02, 0.04, 0.03 ],
    [ 0.00, 0.02, 0.02, 0.03, 0.02 ],
    [ 0.01, 0.02, 0.02, 0.03, 0.02 ],
    [ 0.01, 0.00, 0.01, 0.05, 0.03 ],
];

my $stat = Statistics::Descriptive::Full->new;
# You also have the option of using sparse data structures

for my $ref ( @$data ) {
    $stat->add_data( @$ref );
    printf "Running mean: %f\n", $stat->mean;
    printf "Running stdev: %f\n", $stat->standard_deviation;
}
__END__

Produzione:

C:\Temp> g
Running mean: 0.022000
Running stdev: 0.013038
Running mean: 0.020000
Running stdev: 0.011547
Running mean: 0.020000
Running stdev: 0.010000
Running mean: 0.020000
Running stdev: 0.012566

8

Dai un'occhiata a PDL (pronunciato "piddle!").

Questo è il Perl Data Language, progettato per la matematica di alta precisione e il calcolo scientifico.

Ecco un esempio usando le tue figure ...

use strict;
use warnings;
use PDL;

my $figs = pdl [
    [0.01, 0.01, 0.02, 0.04, 0.03],
    [0.00, 0.02, 0.02, 0.03, 0.02],
    [0.01, 0.02, 0.02, 0.03, 0.02],
    [0.01, 0.00, 0.01, 0.05, 0.03],
];

my ( $mean, $prms, $median, $min, $max, $adev, $rms ) = statsover( $figs );

say "Mean scores:     ", $mean;
say "Std dev? (adev): ", $adev;
say "Std dev? (prms): ", $prms;
say "Std dev? (rms):  ", $rms;


Che produce:

Mean scores:     [0.022 0.018 0.02 0.02]
Std dev? (adev): [0.0104 0.0072 0.004 0.016]
Std dev? (prms): [0.013038405 0.010954451 0.0070710678 0.02]
Std dev? (rms):  [0.011661904 0.009797959 0.0063245553 0.017888544]


Dai un'occhiata a PDL :: Primitive per maggiori informazioni sulla funzione statsover . Questo sembra suggerire che ADEV sia la "deviazione standard".

Tuttavia, forse PRMS (che mostra l'esempio di Statistiche :: Descrittivo di Sinan) o RMS (che mostra l'esempio di NumPy di ​​ars). Immagino che uno di questi tre debba essere corretto ;-)

Per ulteriori informazioni su PDL, dai un'occhiata a:


1
Questo non è un calcolo in corso.
Jake

3

Quanto è grande il tuo array? A meno che non sia lungo un'infinità di elementi, non preoccuparti di ripeterci due volte. Il codice è semplice e facilmente testabile.

La mia preferenza sarebbe quella di utilizzare l' estensione numpy array maths per convertire il tuo array di array in un array numpy 2D e ottenere direttamente la deviazione standard:

>>> x = [ [ 1, 2, 4, 3, 4, 5 ], [ 3, 4, 5, 6, 7, 8 ] ] * 10
>>> import numpy
>>> a = numpy.array(x)
>>> a.std(axis=0) 
array([ 1. ,  1. ,  0.5,  1.5,  1.5,  1.5])
>>> a.mean(axis=0)
array([ 2. ,  3. ,  4.5,  4.5,  5.5,  6.5])

Se questa non è un'opzione e hai bisogno di una soluzione Python pura, continua a leggere ...

Se il tuo array è

x = [ 
      [ 1, 2, 4, 3, 4, 5 ],
      [ 3, 4, 5, 6, 7, 8 ],
      ....
]

Quindi la deviazione standard è:

d = len(x[0])
n = len(x)
sum_x = [ sum(v[i] for v in x) for i in range(d) ]
sum_x2 = [ sum(v[i]**2 for v in x) for i in range(d) ]
std_dev = [ sqrt((sx2 - sx**2)/N)  for sx, sx2 in zip(sum_x, sum_x2) ]

Se si è determinati a eseguire il ciclo dell'array una sola volta, è possibile combinare le somme correnti.

sum_x  = [ 0 ] * d
sum_x2 = [ 0 ] * d
for v in x:
   for i, t in enumerate(v):
   sum_x[i] += t
   sum_x2[i] += t**2

Non è così elegante come la soluzione di comprensione dell'elenco sopra.


In realtà ho a che fare con miliardi di numeri, che è ciò che motiva il mio bisogno di una soluzione efficiente. Grazie!
Alex Reynolds

non si tratta di quanto sia grande il set di dati, ma di quanto SPESSO, devo fare 3500 diversi calcoli di deviazione standard su 500 elementi su ogni calcolo al secondo
PirateApp



1

Ecco un "one-liner", distribuito su più righe, in stile di programmazione funzionale:

def variance(data, opt=0):
    return (lambda (m2, i, _): m2 / (opt + i - 1))(
        reduce(
            lambda (m2, i, avg), x:
            (
                m2 + (x - avg) ** 2 * i / (i + 1),
                i + 1,
                avg + (x - avg) / (i + 1)
            ),
            data,
            (0, 0, 0)))

1
n=int(raw_input("Enter no. of terms:"))

L=[]

for i in range (1,n+1):

    x=float(raw_input("Enter term:"))

    L.append(x)

sum=0

for i in range(n):

    sum=sum+L[i]

avg=sum/n

sumdev=0

for j in range(n):

    sumdev=sumdev+(L[j]-avg)**2

dev=(sumdev/n)**0.5

print "Standard deviation is", dev


1

Mi piace esprimere l'aggiornamento in questo modo:

def running_update(x, N, mu, var):
    '''
        @arg x: the current data sample
        @arg N : the number of previous samples
        @arg mu: the mean of the previous samples
        @arg var : the variance over the previous samples
        @retval (N+1, mu', var') -- updated mean, variance and count
    '''
    N = N + 1
    rho = 1.0/N
    d = x - mu
    mu += rho*d
    var += rho*((1-rho)*d**2 - var)
    return (N, mu, var)

in modo che una funzione one-pass sia simile a questa:

def one_pass(data):
    N = 0
    mu = 0.0
    var = 0.0
    for x in data:
        N = N + 1
        rho = 1.0/N
        d = x - mu
        mu += rho*d
        var += rho*((1-rho)*d**2 - var)
        # could yield here if you want partial results
   return (N, mu, var)

si noti che questo calcola la varianza del campione (1 / N), non la stima imparziale della varianza della popolazione (che utilizza un fattore di normalizzazione 1 / (N-1)). A differenza delle altre risposte, la variabile, varovvero il monitoraggio della varianza corrente non cresce proporzionalmente al numero di campioni. In ogni momento è solo la varianza dell'insieme di campioni visto finora (non c'è una "divisione per n" finale per ottenere la varianza).

In una classe sarebbe simile a questo:

class RunningMeanVar(object):
    def __init__(self):
        self.N = 0
        self.mu = 0.0
        self.var = 0.0
    def push(self, x):
        self.N = self.N + 1
        rho = 1.0/N
        d = x-self.mu
        self.mu += rho*d
        self.var += + rho*((1-rho)*d**2-self.var)
    # reset, accessors etc. can be setup as you see fit

Questo funziona anche per i campioni ponderati:

def running_update(w, x, N, mu, var):
    '''
        @arg w: the weight of the current sample
        @arg x: the current data sample
        @arg mu: the mean of the previous N sample
        @arg var : the variance over the previous N samples
        @arg N : the number of previous samples
        @retval (N+w, mu', var') -- updated mean, variance and count
    '''
    N = N + w
    rho = w/N
    d = x - mu
    mu += rho*d
    var += rho*((1-rho)*d**2 - var)
    return (N, mu, var)

0

Ecco un esempio pratico di come potresti implementare una deviazione standard in esecuzione con python e numpy:

a = np.arange(1, 10)
s = 0
s2 = 0
for i in range(0, len(a)):
    s += a[i]
    s2 += a[i] ** 2 
    n = (i + 1)
    m = s / n
    std = np.sqrt((s2 / n) - (m * m))
    print(std, np.std(a[:i + 1]))

Questo stamperà la deviazione standard calcolata e una deviazione standard di controllo calcolata con numpy:

0.0 0.0
0.5 0.5
0.8164965809277263 0.816496580927726
1.118033988749895 1.118033988749895
1.4142135623730951 1.4142135623730951
1.707825127659933 1.707825127659933
2.0 2.0
2.29128784747792 2.29128784747792
2.5819888974716116 2.581988897471611

Sto solo usando la formula descritta in questo thread:

stdev = sqrt((sum_x2 / n) - (mean * mean)) 
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.