Algoritmi per il calcolo della varianza

Gli algoritmi per il calcolo della varianza giocano un ruolo molto importante nella statistica computazionale. Una difficoltà chiave nel progetto di un buon algoritmo per questo problema è che le formule per la varianza possono includere somme di quadrati, che possono condurre a instabilità numerica così come overflow aritmetico quando vengono trattati grandi valori.

Algoritmo banale modifica

Una formula per calcolare la varianza di un'intera popolazione di dimensione N è:

 

Usando la correzione di Bessel per calcolare una stima priva di bias della varianza della popolazione da un campione finito di n osservazioni, la formula è:

 

Pertanto, un algoritmo banale per calcolare la stima della varianza è dato da:

def stimaVarianza(x):
   n = length(x)
   somma = 0
   sommaQuad = 0
   for xi in x:
      somma = somma + xi
      sommaQuad = sommaQuad + xi^2
   return (sommaQuad - somma^2 / n) / (n - 1)

Questo algoritmo può facilmente essere adattato al calcolo della varianza di una popolazione finita: basta dividere per N invece di n − 1 nell'ultima linea.

Poiché   e   possono essere numeri molto simili, la cancellazione può far sì che la precisione del risultato sia molto inferiore alla precisione intrinseca dell'aritmetica in virgola mobile usata per effettuare il calcolo. Così, questo algoritmo non deve essere usato nella pratica.[1][2] Questo è particolarmente svantaggioso se la deviazione standard è piccola rispetto alla media. Tuttavia, l'algoritmo può essere migliorato adottando il metodo della media ipotizzata.

Calcolo con dati traslati modifica

Possiamo usare una proprietà della varianza per evitare la cancellazione numerica in questa formula, poiché la varianza è invariante rispetto ai cambiamenti di changes in un parametro di locazione

 

con   costante qualunque, il che conduce alla nuova formula

 

e più vicino è   al valore medio, più accurato il risultato sarà, ma soltanto lo scegliere un valore compreso nell'intervallo dei campioni garantirà la stabilitata desiderata. Se i valori   sono piccoli, allora non ci sono problemi con la somma dei loro quadrati; al contrario, se essi sono grandi, ciò significa necessariamente che la varianza è anch'essa grande. In ogni caso, il secondo termine nella formula è sempre più piccolo del primo, quindi nessuna cancellazione può avvenire.[2]

Se prendiamo solo il primo campione come  , l'algoritmo può essere scritto nel linguaggio di programmazione Python come segue

def shifted_data_variance(data):
   if len(data) == 0:
      return 0
   K = data[0]
   n = 0
   sum_ = 0
   sum_sqr = 0
   for x in data:
      n = n + 1
      sum_ += x - K
      sum_sqr += (x - K) * (x - K)
   variance = (sum_sqr - (sum_ * sum_)/n)/(n - 1)
   # use n instead of (n-1) if want to compute the exact variance of the given data
   # use (n-1) if data are samples of a larger population
   return variance

questa formula facilitata così il calcolo incrementale, che può essere espresso come segue

K = 0
n = 0
ex = 0
ex2 = 0
 
def add_variable(x):
    if (n == 0):
      K = x
    n = n + 1
    ex += x - K
    ex2 += (x - K) * (x - K)
 
def remove_variable(x):
    n = n - 1
    ex -= (x - K)
    ex2 -= (x - K) * (x - K)
 
def get_meanvalue():
   return K + ex / n

def get_variance():
    return (ex2 - (ex*ex)/n) / (n-1)

Algoritmo a due passi modifica

Un approccio alternativo, usando una formula diversa per la varianza, consiste prima nel calcolare la media campione,

 ,

e poi calcolare la somma dei quadrati delle differenze dalla media,

 ,

dove s è la deviazione standard. Ciò è dato dal seguente codice sorgente:

def two_pass_variance(data):
    n = 0
    sum1 = 0
    sum2 = 0
    
    for x in data:
        n += 1
        sum1 += x
    
    mean = sum1 / n

    for x in data:
        sum2 += (x - mean)*(x - mean)
    
    variance = sum2 / (n - 1)
    return variance

Questo algoritmo è numericamente stabile se n è piccolo.[1][3] Tuttavia, i risultati di entrambi questi algoritmi semplici ("banale" e "a due passi") possono dipendere non commutativamente dall'ordinamento dei dati e possono dare scarsi risultati per insiemi grandi di dati a causa di errori di arrotondamento ripetuti che si accumulano nelle somme. Tecniche quali la somma compensata possono essere usate per ridurre questo errore.

Variante compensata modifica

La versione con somma compensata dell'algoritmo visto sopra è:[4]

def compensated_variance(data):
    n = 0
    sum1 = 0
    for x in data:
        n += 1
        sum1 += x
    mean = sum1/n
     
    sum2 = 0
    sum3 = 0
    for x in data:
        sum2 += (x - mean)**2
        sum3 += (x - mean)
    variance = (sum2 - sum3**2/n)/(n - 1)
    return variance

Algoritmo in linea modifica

Spesso è utile essere in grado di calcolare la varianza in un passo singolo, elaborando ogni valore   una sola volta; per esempio, quando i dati vengono raccolti senza abbastanza spazio per conservare tutti i valori, o quando i costi dell'accesso alla memoria sono preponderanti tra quelli del calcolo. Per un tale algoritmo in linea, è richiesta una relazione di ricorrenza tra quantità dalle quali le statistiche richieste possono essere calcolate in un modo numericamente stabile.

Le seguenti formule possono essere usate per aggiornare la media e la varianza (stimata) della sequenza, per un elemento addizionale  . Qui,   denota la media campione dei primi   campioni  ,   la loro varianza campione e   la varianza della loro popolazione.

 
 
 

Queste formule soffrono di instabilità numerica. Una quantità migliore per l'aggiornamento è la somma dei quadrati delle differenze dalla media corrente  , qui indicate come  :

 
 
 

Un algoritmo numericamente stabile per la varianza campione è indicato sotto. Esso calcola anche la media. Questo algoritmo è dovuto a Knuth,[5] che cita Welford,[6] ed è stato accuratamente analizzato.[7][8] È anche comune denotare   e  .[9]

def online_variance(data):
    n = 0
    mean = 0.0
    M2 = 0.0
     
    for x in data:
        n += 1
        delta = x - mean
        mean += delta/n
        M2 += delta*(x - mean)

    if n < 2:
        return float('nan')
    else:
        return M2 / (n - 1)

Questo algoritmo è molto meno incline a perdita di precisione a causa della cancellazione numerica, ma potrebbe non essere efficiente a causa dell'operazione di divisione all'interno del ciclo. Per un algoritmo a due passi particolarmente robusto per calcolare la varianza, si può prima calcolare e sottrarre una stima della media e poi utilizzare questo algoritmo sui residui.

Sotto, l'algoritmo parallelo illustra come unire più insiemi di statistiche calcolate in linea.

Algoritmo incrementale ponderato modifica

L'algoritmo può essere esteso per gestire pesi campione disuguali, sostituendo il semplice contatore   con la somma dei pesi vista finora. West (1979)[10] suggerisce questo algoritmo incrementale:

def weighted_incremental_variance(dataWeightPairs):
    sumweight = 0
    mean = 0
    M2 = 0

    for x, weight in dataWeightPairs:  # Alternatively "for x, weight in zip(data, weights):"
        temp = weight + sumweight
        delta = x - mean
        R = delta * weight / temp
        mean += R
        M2 += sumweight * delta * R  # Alternatively, "M2 = M2 + weight * delta * (x−mean)"
        sumweight = temp

    variance_n = M2/sumweight
    variance = variance_n * len(dataWeightPairs)/(len(dataWeightPairs) - 1)

Algoritmo parallelo modifica

Chan e altri.[4] Notare che l'algoritmo in linea indicato sopra è un caso particolare di algoritmo che funziona per ogni partizione del campione   negli insiemi  ,  :

 
 
 .

Ciò può essere utile quando, per esempio, più unità di elaborazione possono essere assegnate a parti discrete di input.

Il metodo di Chan per stimare la media è instabile numericamente quando   ed entrambi sono grandi, poiché l'errore numerico in   non è ridotto in modo da essere nel caso  . In tali casi, è preferibile che  .

Esempio modifica

Supponiamo che tutte le operazioni in virgola mobile usino l'aritmetica con lo standard IEEE 754 con precisione doppia. Consideriamo il campione   da una popolazione infinita. Sulla base di questo campione, la media stimata della popolazione è 10 e la stima priva di bias della varianza della popolazione è 30. Entrambi gli algoritmi, quello "banale" e quello "a due passi", calcolano questi valori correttamente. Ora, invece, consideriamo il campione  , che dà luogo alla stessa varianza stimata come per il primo campione. L'algoritmo "a due passi" calcola correttamente questa varianza stimata, ma l'algoritmo "banale" restituisce   invece di 30. Mentre questa perdita di precisione può essere tollerabile e considerata come un difetto minore dell'algoritmo "a due passi", è facile trovare dati che rivelano un grave difetto nell'algoritmo "banale": Prendiamo il campione  . Nuovamente, la varianza stimata della popolazione, di valore 30, è calcolata correttamente dall'algoritmo "a due passi", ma l'algoritmo "banale" ora calcola  . Ciò costituisce un serio problema con l'algoritmo "banale" ed è dovuto alla cancellazione numerica nella sottrazione di due numeri simili nella fase finale dell'algoritmo.

Statistiche di ordine superiore modifica

Terriberry[11] estende le formule di Chan al calcolo del terzo e quarto momento centrale, necessarie per esempio quando si stimano asimmetria e curtosi:

 
 

Qui, gli   sono nuovamente le somme di potenze di differenze dalla la media  , avendo

asimmetria:  
cutrosi:  

Per il caso incrementale (cioè,  ), ciò si riduce a:

 
 
 
 
 

Conservando il valore  , è necessaria una sola operazione di divisione e le statistiche di ordine superiore possono così essere calcolate per poco costo incrementale.

Un esempio dell'algoritmo in linea per la curtosi implementato come descritto è:

def online_kurtosis(data):
    n = 0
    mean = 0
    M2 = 0
    M3 = 0
    M4 = 0

    for x in data:
        n1 = n
        n = n + 1
        delta = x - mean
        delta_n = delta / n
        delta_n2 = delta_n * delta_n
        term1 = delta * delta_n * n1
        mean = mean + delta_n
        M4 = M4 + term1 * delta_n2 * (n*n - 3*n + 3) + 6 * delta_n2 * M2 - 4 * delta_n * M3
        M3 = M3 + term1 * delta_n * (n - 2) - 3 * delta_n * M2
        M2 = M2 + term1

    kurtosis = (n*M4) / (M2*M2) - 3
    return kurtosis

Pébay[12] estende ulteriormente questi risultati per momenti centrali di ordine arbitrario, per il caso incrementale e per il caso di coppie. Si possono trovare formule simili per la covarianza.

Choi e Sweetman [13] offrono due metodi alternativi per calcolare l'asimmetria e la curtosi, ognuno dei quali può far risparmiare requisiti di memoria del computer e tempo di occupazione della CPU in certe applicazioni. Il primo approccio consiste nel calcolare i momenti statistici separando i dati in pacchetti e poi calcolare i momenti dalla geometria dell'istogramma risultante, che effettivamente diventa un algoritmo a un passo per momenti superiori. Un vantaggio è che il calcolo dei momenti statistici può essere effettuato con accuratezza arbitraria tale che i calcoli possono essere portati, per esempio, alla precisione del formato di memorizzazione dei dati o alla capacità originale dell'hardware. Un istogramma relativo ad una variabile casuale può essere costruito nel modo convenzionale: l'intervallo di valori potenziali è diviso in pacchetti e il numero di occorrenze all'interno di ogni pacchetto è contato e graficato in modo tale che l'area di ogni rettangolo sia uguale alla porzione di valori campione in questo pacchetto:

 

dove   e   rappresentano la frequenza e la frequenza relativa per il pacchetto   e   è l'area totale dell'istogramma. Dopo questa normalizzazione, i momenti di origine zero (raw moments)   e i momenti centrali di   possono essere calcolati dal relativo istogramma:

 
 

dove la sovrascritta   indica che i momenti sono calcolati dall'istogramma. Per un pacchetto costante di larghezza   queste due espressioni possono essere semplificate usando  :

 
 

Il secondo approccio, da Choi and Sweetman [13], consiste in una metodologia analitica per combinare momenti statistici da segmenti individuali di una storia temporale tale che i momenti complessivi risultanti siano quelli di una storia temporale completa. Questa metodologia potrebbe essere utilizzata per il calcolo parallelo di momenti statistici con successiva combinazione di quei momenti, o per la combinazione di momenti statistici calcolati in tempi sequenziali.

Se gli insiemi   di momenti statistici sono noti:   per  , allora ogni   può essere espresso in termini dei momenti di origine zero (raw moments) equivalenti   :

 

dove, generalmente, come   si prende la durata della storia temporale  , o il numero di punti se   è costante.

Il vantaggio di esprimere i momenti statistici in termini di   è che gli insiemi   possono essere combinati per addizione e non c'è un limite superiore per il valore di  .

 

dove la sovrascritta   rappresenta la storia temporale   concatenata o combinata. Questi valori combinati di   possono essere trasformati inversamente in momenti di origine zero (raw moments) rappresentando la storia temporale concatenata completa

 

relazioni note tra i momenti di origine zero (raw moments) ( ) e il momento centrale   sono allora usate per calcolare i momenti centrali della storia temporale concatenata. Infine, i momenti statistici della storia concatenata vengono calcolati dai momenti centrali:

 

Covarianza modifica

Si possono utilizzare algoritmi molto simili per calcolare la covarianza. L'algoritmo "banale" è:

 

Per l'algoritmo scritto sopra, si può utilizzare il seguente codice Python:

def naive_covariance(data1, data2):
    n = len(data1)
    sum12 = 0
    sum1 = sum(data1)
    sum2 = sum(data2)

    for i in range(n):
        sum12 += data1[i]*data2[i]

    covariance = (sum12 - sum1*sum2 / n) / n
    return covariance

Come per la variaznza, la covarianza di due variabili casuali è anche invariante per traslazione, allora, dati due valori costanti   e   qualsiasi, si può scrivere:

 

e, nuovamente, scegliendo un valore all'interno dell'intervallo dei valori stabilizzerà la formula contro la cancellazione numerica in modo da renderlo più robusto nei confronti delle grandi somme. Prendendo il primo valore in ogni insieme di dati, l'algoritmo può essere scritto come segue:

def shifted_data_covariance(dataX, dataY):
   n = len(dataX)
   if (n < 2):
     return 0
   Kx = dataX[0]
   Ky = dataY[0]
   Ex = 0
   Ey = 0
   Exy = 0
   for i in range(n):
      Ex += dataX[i] - Kx
      Ey += dataY[i] - Ky
      Exy += (dataX[i] - Kx) * (dataY[i] - Ky)
   return (Exy - Ex * Ey / n) / n

L'algoritmo a due passi prima calcola le medie campione e dopo la covarianza:

 
 
 

L'algoritmo a due passi può essere scritto come segue:

def two_pass_covariance(data1, data2):
    n = len(data1)

    mean1 = sum(data1) / n
    mean2 = sum(data2) / n

    covariance = 0

    for i in range(n):
        a = data1[i] - mean1
        b = data2[i] - mean2
        covariance += a*b / n

    return covariance

Una versione compensata un po' più accurata esegue l'algoritmo "banale" completo sui residui. Le somme finali   e   dovrebbero essere zero, ma il secondo passaggio compensa qualsiasi piccolo errore.

Una lieve modifica dell'algoritmo in linea per il calcolo della varianza produce un algoritmo in linea per la covarianza:

def online_covariance(data1, data2):
    mean1 = mean2 = 0.
    M12 = 0.
    n = len(data1)
    for i in range(n):
        delta1 = (data1[i] - mean1) / (i + 1)
        mean1 += delta1
        delta2 = (data2[i] - mean2) / (i + 1)
        mean2 += delta2
        M12 += i * delta1 * delta2 - M12 / (i + 1)
    return n / (n - 1.) * M12

Esiste un algoritmo stabile a un passo, simile a quello visto sopra, che calcola il co-momento  :

 
 
 

L'asimmetria apparente in quest'ultima equazione è dovuta al fatto che  , così entrambi i termini di aggiornamento sono pari a  . Un'accuratezza ancora maggiore può essere ottenuta calcolando prima le medie e dopo usando l'algoritmo stabile a un passo sui residui.

Così, possiamo calcolare la covarianza come

 

Allo stesso modo, vi è una formula per combinare le covarianze di due insiemi che può essere utilizzata per parallelizzare calcolo:

 .

Note modifica

  1. ^ a b Bo Einarsson, Accuracy and Reliability in Scientific Computing, SIAM, 1º agosto 2005, p. 47, ISBN 978-0-89871-584-2. URL consultato il 17 febbraio 2013.
  2. ^ a b T.F.Chan, G.H. Golub and R.J. LeVeque, "Algorithms for computing the sample variance: Analysis and recommendations", The American Statistician, 37 (PDF), 1983, pp. 242–247.
  3. ^ Nicholas Higham, Accuracy and Stability of Numerical Algorithms (2 ed) (Problem 1.10), SIAM, 2002.
  4. ^ a b Tony F. Chan, Gene H. Golub e Randall J. LeVeque, Updating Formulae and a Pairwise Algorithm for Computing Sample Variances. (PDF), in Technical Report STAN-CS-79-773, Department of Computer Science, Stanford University, 1979.
  5. ^ Donald E. Knuth (1998). The Art of Computer Programming, volume 2: Seminumerical Algorithms, 3rd edn., p. 232. Boston: Addison-Wesley.
  6. ^ B. P. Welford (1962)."Note on a method for calculating corrected sums of squares and products". Technometrics 4(3):419–420.
  7. ^ Chan, Tony F.; Golub, Gene H.; LeVeque, Randall J. (1983). Algorithms for Computing the Sample Variance: Analysis and Recommendations. The American Statistician 37, 242-247. https://www.jstor.org/stable/2683386
  8. ^ Ling, Robert F. (1974). Comparison of Several Algorithms for Computing Sample Means and Variances. Journal of the American Statistical Association, Vol. 69, No. 348, 859-866. DOI10.2307/2286154
  9. ^ http://www.johndcook.com/standard_deviation.html
  10. ^ D. H. D. West (1979). Communications of the ACM, 22, 9, 532-535: Updating Mean and Variance Estimates: An Improved Method
  11. ^ Timothy B. Terriberry, Computing Higher-Order Moments Online, 2007. URL consultato il 29 aprile 2019 (archiviato dall'url originale l'8 aprile 2019).
  12. ^ Philippe Pébay, Formulas for Robust, One-Pass Parallel Computation of Covariances and Arbitrary-Order Statistical Moments (PDF), in Technical Report SAND2008-6212, Sandia National Laboratories, 2008. URL consultato il 17 giugno 2016 (archiviato dall'url originale il 9 ottobre 2022).
  13. ^ a b Muenkeun Choi e Bert Sweetman, Efficient Calculation of Statistical Moments for Structural Health Monitoring (PDF), 2010. URL consultato il 17 giugno 2016 (archiviato dall'url originale il 3 marzo 2016).

Voci correlate modifica

Collegamenti esterni modifica

  Portale Statistica: accedi alle voci di Wikipedia che trattano di statistica