it-swarm.com.de

Wie lässt sich eine laufende Standardabweichung effizient berechnen?

Ich habe eine Reihe von Nummernlisten, z.

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

Was ich gerne tun würde, ist, den Mittelwert und die Standardabweichung für jeden Index einer Liste über alle Array-Elemente effizient zu berechnen.

Um das zu erreichen, habe ich das Array durchlaufen und den Wert an einem bestimmten Index einer Liste summiert. Am Ende teile ich jeden Wert in meiner "Durchschnittsliste" durch n.

Um die Standardabweichung zu machen, schlaufe ich erneut durch, jetzt habe ich den Mittelwert berechnet.

Ich möchte vermeiden, zweimal durch das Array zu gehen, einmal für den Mittelwert und dann einmal für die SD (nachdem ich einen Mittelwert habe). 

Gibt es eine effiziente Methode, um beide Werte zu berechnen und das Array nur einmal durchzugehen? Jeder Code in einer interpretierten Sprache (z. B. Perl oder Python) oder Pseudocode ist in Ordnung.

74
Alex Reynolds

Die Antwort ist, den Welford-Algorithmus zu verwenden, der nach den "naiven Methoden" in:

Es ist numerisch stabiler als die in anderen Antworten empfohlene Sammlung von Quadraten. Die Stabilität ist nur dann wirklich wichtig, wenn Sie viele Werte haben, die nahe beieinander liegen, da sie zu einer sogenannten " katastrophalen Löschung " in der Fließkommaliteratur führen.

Möglicherweise möchten Sie auch die Differenz zwischen der Division durch die Anzahl der Abtastwerte (N) und N-1 in der Varianzberechnung (Quadratabweichung) auffrischen. Die Division durch N-1 führt zu einer unvoreingenommenen Schätzung der Abweichung von der Stichprobe, während die Division durch N im Durchschnitt die Varianz unterschätzt (da die Abweichung zwischen dem Stichprobenmittelwert und dem wahren Mittelwert nicht berücksichtigt wird).

Ich habe zu diesem Thema zwei Blogeinträge geschrieben, in denen weitere Details beschrieben werden, darunter das Löschen früherer Werte online:

Sie können auch meine Java-Implementierung betrachten. Die Javadoc-, Quell- und Komponententests sind alle online:

101
Bob Carpenter

Die grundlegende Antwort besteht darin, die Summe von x (nennen Sie 'sum_x1') und x 2 (nennen Sie 'sum_x2'), während Sie gehen. Der Wert der Standardabweichung ist dann:

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

woher

mean = sum_x / n

Dies ist die Standardabweichung der Probe. Sie erhalten die Populationsstandardabweichung mit 'n' anstelle von 'n - 1' als Divisor.

Möglicherweise müssen Sie sich um die numerische Stabilität der Differenz zwischen zwei großen Zahlen kümmern, wenn Sie mit großen Stichproben arbeiten. Weitere Informationen finden Sie in den externen Antworten (Wikipedia usw.).

69

Vielleicht nicht das, was Sie gefragt haben, aber ... Wenn Sie ein numpy-Array verwenden, erledigt es die Arbeit effizient für Sie:

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 ]

Übrigens gibt es in diesem Blogbeitrag einige interessante Diskussionen und Kommentare zu One-Pass-Methoden für Berechnungsmittel und Varianzen:

26
ars

Hier ist eine wörtliche reine Python-Übersetzung der Welford-Algorithmus-Implementierung aus http://www.johndcook.com/standard_deviation.html :

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

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

Verwendungszweck:

rs = RunningStats()
rs.Push(17.0);
rs.Push(19.0);
rs.Push(24.0);

mean = rs.mean();
variance = rs.variance();
stdev = rs.standard_deviation();
23
Marc Liyanage

Das Python Runstats Module ist genau für diese Art von Dingen. Installiere Runstats von PyPI:

pip install runstats

Runstats-Zusammenfassungen können den Mittelwert, die Varianz, die Standardabweichung, die Schiefe und die Kurtosis in einem einzigen Durchgang von Daten erzeugen. Wir können dies verwenden, um Ihre "laufende" Version zu erstellen.

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

Statistikzusammenfassungen basieren auf der Methode von Knuth und Welford zur Berechnung der Standardabweichung in einem Durchgang, wie in Art of Computer Programming, Band 2, S. 2 beschrieben. 232, 3. Auflage. Der Vorteil davon ist numerisch stabile und genaue Ergebnisse.

Disclaimer: Ich bin der Autor des Python-Runstats-Moduls.

10
GrantJ

Schauen Sie sich dazu PDL (ausgesprochen "piddle!") An. 

Dies ist die Perl Data Language, die für hochpräzise Mathematik und wissenschaftliches Rechnen entwickelt wurde.

Hier ein Beispiel mit Ihren Zahlen.

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;


Was produziert:

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]


Weitere Informationen über die. Finden Sie unter PDL :: Primitive statsover Funktion. Dies scheint darauf hinzudeuten, dass ADEV die "Standardabweichung" ist. 

Es kann jedoch PRMS (das Sinans Statistics :: Descriptive-Beispiel zeigt) oder RMS (das von NumPy-Beispiel von ars zeigt). Ich denke, eins von diesen drei muss stimmen ;-)

Weitere PDL-Informationen finden Sie unter:

8
draegtun

Statistics :: Descriptive ist ein sehr anständiges Perl-Modul für diese Art von Berechnungen:

#!/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__

Ausgabe:

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
7
Sinan Ünür

Wie groß ist dein Array? Wenn es nicht zig Millionen von Elementen gibt, machen Sie sich keine Sorgen, es zweimal durchzublättern. Der Code ist einfach und leicht zu testen.

Ich würde es vorziehen, die mathematische Erweiterung numpy array zu verwenden, um Ihr Array von Arrays in ein numpy 2D-Array zu konvertieren und die Standardabweichung direkt abzurufen:

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

Wenn dies keine Option ist und Sie eine reine Python-Lösung benötigen, lesen Sie weiter ...

Wenn dein Array ist 

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

Dann ist die Standardabweichung:

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

Wenn Sie das Array nur einmal durchlaufen möchten, können die laufenden Summen kombiniert werden.

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

Dies ist bei weitem nicht so elegant wie die Listenverständnislösung oben.

3
Stephen Simmons

Ich denke, dieses Problem wird Ihnen helfen. Standardabweichung

2
peterdemin
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
Anuraag

Sie können sich den Wikipedia-Artikel über Standardabweichung ansehen, insbesondere den Abschnitt über schnelle Berechnungsmethoden.

Es gibt auch einen Artikel, der Python verwendet. Sie sollten den Code ohne große Änderungen verwenden können: Subliminal Messages - Standardabweichungen ausführen .

Wie die folgende Antwort beschreibt: Bietet Pandas/Scipy/Numpy eine kumulative Standardabweichungsfunktion? Das Python Pandas-Modul enthält eine Methode zur Berechnung der laufenden oder kumulativen Standardabweichung . Dazu müssen Sie Ihre Daten in einen Pandas-Datenrahmen konvertieren (oder eine Serie, wenn es sich um 1D handelt.) ), aber dafür gibt es Funktionen. 

1
Ramon Crehuet

Hier ist ein "Einzeiler", der sich über mehrere Zeilen erstreckt und im funktionalen Programmierstil ist:

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

Ich möchte das Update so ausdrücken:

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)

damit eine One-Pass-Funktion so aussehen würde:

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)

beachten Sie, dass dies die Stichprobenvarianz (1/N) berechnet, nicht die unverzerrte Schätzung der Populationsvarianz (die einen Normalisierungsfaktor von 1/(N-1) verwendet). Im Gegensatz zu den anderen Antworten wächst die Variable var, die die laufende Varianz verfolgt, nicht proportional zur Anzahl der Stichproben. Zu allen Zeiten ist es nur die Varianz des Satzes von Abtastungen, die bisher gesehen wurden (es gibt keine endgültige "Division durch n", um die Varianz zu erhalten).

In einer Klasse würde es so aussehen:

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

Dies funktioniert auch für gewichtete Proben:

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
Dave