Rilevamento del periodo di una serie storica generica


53

Questo post è la continuazione di un altro post correlato a un metodo generico per il rilevamento anomalo nelle serie temporali . Fondamentalmente, a questo punto mi interessa un modo robusto per scoprire la periodicità / stagionalità di una serie storica generica influenzata da un sacco di rumore. Dal punto di vista degli sviluppatori, vorrei un'interfaccia semplice come:

unsigned int discover_period(vector<double> v);

Dove si vtrova l'array che contiene i campioni e il valore restituito è il periodo del segnale. Il punto principale è che, ancora una volta, non posso assumere alcuna ipotesi riguardo al segnale analizzato. Ho già provato un approccio basato sull'autocorrelazione del segnale (rilevando i picchi di un correlogramma), ma non è robusto come vorrei.


1
Hai provato xts :: periodicity?
Fabrício,

Risposte:


49

Se davvero non hai idea di quale sia la periodicità, probabilmente l'approccio migliore è trovare la frequenza corrispondente al massimo della densità spettrale. Tuttavia, lo spettro alle basse frequenze sarà influenzato dall'andamento, quindi è necessario prima penalizzare la serie. La seguente funzione R dovrebbe fare il lavoro per la maggior parte delle serie. È tutt'altro che perfetto, ma l'ho testato su alcune dozzine di esempi e sembra funzionare bene. Restituirà 1 per i dati che non hanno una forte periodicità e la durata del periodo altrimenti.

Aggiornamento: versione 2 della funzione. Questo è molto più veloce e sembra essere più robusto.

find.freq <- function(x)
{
    n <- length(x)
    spec <- spec.ar(c(x),plot=FALSE)
    if(max(spec$spec)>10) # Arbitrary threshold chosen by trial and error.
    {
        period <- round(1/spec$freq[which.max(spec$spec)])
        if(period==Inf) # Find next local maximum
        {
            j <- which(diff(spec$spec)>0)
            if(length(j)>0)
            {
                nextmax <- j[1] + which.max(spec$spec[j[1]:500])
                period <- round(1/spec$freq[nextmax])
            }
            else
                period <- 1
        }
    }
    else
        period <- 1
    return(period)
}

Grazie. Ancora una volta, proverò questo approccio il prima possibile e scriverò qui i risultati finali.
gianluca,

2
La tua idea è abbastanza buona, ma nel mio caso non riesce a rilevare la periodicità di una serie temporale davvero semplice (e non così rumorosa) come dl.dropbox.com/u/540394/chart.png . Con il mio approccio "empirico" (basato sull'autocorrelazione), il semplice algoritmo che ho scritto restituisce un periodo esatto di 1008 (avere un campione ogni 10 minuti, questo significa 1008/24/6 = 7, quindi una periodicità settimanale). I miei problemi principali sono: 1) È troppo lento per convergere (richiede molti dati storici) e ho bisogno di un approccio reattivo e online; 2) È inefficiente da un punto di vista dell'utilizzo della memoria; 3) Non è affatto robusto;
gianluca,

Grazie. Sfortunatamente, questo non funziona ancora come mi aspetterei. Per la stessa serie temporale del precedente commento restituisce 166, che è solo parzialmente giusto (dal mio punto di vista, l'evidente periodo settimanale è più interessante). E usando una serie temporale molto rumorosa, come questa dl.dropbox.com/u/540394/chart2.png (un'analisi della finestra del ricevitore TCP), la funzione restituisce 10, mentre mi aspetterei 1 (non riesco a vedere alcun ovvio periodicità). A proposito, so che sarà davvero difficile trovare quello che sto cercando, dato che ho a che fare con segnali troppo diversi.
gianluca,

166 non è una stima errata di 168. Se sai che i dati vengono osservati ogni ora con uno schema settimanale, allora perché stimare la frequenza?
Rob Hyndman,

5
Una versione migliorata è inclusa nel pacchetto di previsioni comefindfrequency
Rob Hyndman,

10

Se ti aspetti che il processo sia stazionario - la periodicità / stagionalità non cambierà nel tempo - allora qualcosa come un periodogramma Chi-quadrato (vedi ad esempio Sokolove e Bushell, 1978) potrebbe essere una buona scelta. È comunemente usato nell'analisi dei dati circadiani che possono contenere quantità estremamente elevate di rumore, ma si prevede che abbiano periodicità molto stabili.

Questo approccio non presuppone la forma della forma d'onda (a parte il fatto che è coerente da un ciclo all'altro), ma richiede che qualsiasi rumore sia di media costante e non correlato al segnale.

chisq.pd <- function(x, min.period, max.period, alpha) {
N <- length(x)
variances = NULL
periods = seq(min.period, max.period)
rowlist = NULL
for(lc in periods){
    ncol = lc
    nrow = floor(N/ncol)
    rowlist = c(rowlist, nrow)
    x.trunc = x[1:(ncol*nrow)]
    x.reshape = t(array(x.trunc, c(ncol, nrow)))
    variances = c(variances, var(colMeans(x.reshape)))
}
Qp = (rowlist * periods * variances) / var(x)
df = periods - 1
pvals = 1-pchisq(Qp, df)
pass.periods = periods[pvals<alpha]
pass.pvals = pvals[pvals<alpha]
#return(cbind(pass.periods, pass.pvals))
return(cbind(periods[pvals==min(pvals)], pvals[pvals==min(pvals)]))
}

x = cos( (2*pi/37) * (1:1000))+rnorm(1000)
chisq.pd(x, 2, 72, .05)

Le ultime due righe sono solo un esempio, dimostrando che può identificare il periodo di una pura funzione trigonometrica, anche con un sacco di rumore additivo.

Come scritto, l'ultimo argomento ( alpha) nella chiamata è superfluo, la funzione restituisce semplicemente il periodo "migliore" che riesce a trovare; decomprimere la prima returnaffermazione e commentare la seconda per fare in modo che restituisca un elenco di tutti i periodi significativi a livello alpha.

Questa funzione non esegue alcun tipo di controllo di integrità per assicurarsi di aver inserito periodi identificabili, né funziona (può) con periodi frazionari, né esiste un controllo di confronto multiplo integrato se si decide di guarda più periodi. Ma a parte questo dovrebbe essere ragionevolmente robusto.


Sembra interessante ma non capisco l'output, non mi dice dove inizia il periodo e la maggior parte dei valori di 1.
Herman Toothrot

3

Potresti voler definire ciò che desideri più chiaramente (a te stesso, se non qui). Se quello che stai cercando è il periodo stazionario più statisticamente significativo contenuto nei tuoi dati rumorosi, ci sono essenzialmente due percorsi da prendere:

1) calcolare una stima di autocorrelazione robusta e prendere il coefficiente massimo
2) calcolare una stima di densità spettrale di potenza robusta e prendere il massimo dello spettro

Il problema con # 2 è che per qualsiasi serie temporale rumorosa, otterrai una grande quantità di potenza alle basse frequenze, rendendo difficile la distinzione. Esistono alcune tecniche per risolvere questo problema (vale a dire pre-sbiancamento, quindi stimare il PSD), ma se il periodo reale dai dati è abbastanza lungo, il rilevamento automatico sarà incerto.

La soluzione migliore è probabilmente quella di implementare una solida routine di autocorrelazione come quella riportata nel capitolo 8.6, 8.7 in Robust Statistics - Theory and Methods di Maronna, Martin e Yohai. Anche la ricerca di "robusto durbin-levinson" produrrà alcuni risultati.

Se stai solo cercando una risposta semplice, non sono sicuro che esista. Il rilevamento dei periodi nelle serie temporali può essere complicato e richiedere una routine automatizzata in grado di eseguire magie può essere troppo.


Grazie per le tue preziose informazioni, guarderò sicuramente quel libro.
gianluca,

3

È possibile utilizzare la teoria di Hilbert Transformation di DSP per misurare la frequenza istantanea dei dati. Il sito http://ta-lib.org/ ha un codice open source per misurare il periodo di ciclo dominante dei dati finanziari; la relativa funzione si chiama HT_DCPERIOD; potresti essere in grado di utilizzare questo o adattare il codice ai tuoi scopi.


3

Un approccio diverso potrebbe essere la decomposizione della modalità empirica. Il pacchetto R si chiama EMD sviluppato dall'inventore del metodo:

require(EMD)
ndata <- 3000  
tt2 <- seq(0, 9, length = ndata)  
xt2 <- sin(pi * tt2) + sin(2* pi * tt2) + sin(6 * pi * tt2) + 0.5 * tt2  
try <- emd(xt2, tt2, boundary = "wave")  
### Ploting the IMF's  
par(mfrow = c(try$nimf + 1, 1), mar=c(2,1,2,1))  
rangeimf <- range(try$imf)  
for(i in 1:try$nimf) {  
plot(tt2, try$imf[,i], type="l", xlab="", ylab="", ylim=rangeimf, main=paste(i, "-th IMF", sep="")); abline(h=0)  
}  
plot(tt2, try$residue, xlab="", ylab="", main="residue", type="l", axes=FALSE); box()

Il metodo è stato marchiato "Empirical" per una buona ragione e sussiste il rischio che le funzioni della modalità intrinseca (i singoli componenti additivi) si confondano. D'altra parte il metodo è molto intuitivo e può essere utile per una rapida ispezione visiva della ciclicità.


0

In riferimento al post di Rob Hyndman sopra https://stats.stackexchange.com/a/1214/70282

La funzione find.freq funziona perfettamente. Sul set di dati giornaliero che sto usando, ha funzionato correttamente la frequenza a 7.

Quando l'ho provato solo nei giorni della settimana, ha indicato che la frequenza è 23, che è notevolmente vicina a 21.42857 = 29.6 * 5/7, che è il numero medio di giorni lavorativi in ​​un mese. (O al contrario 23 * 7/5 è 32.)

Guardando indietro ai miei dati giornalieri, ho sperimentato un sospetto di prendere il primo periodo, fare la media per quello e quindi trovare il periodo successivo, ecc. Vedi sotto:

find.freq.all = funzione (x) {  
  f = find.freq (x);
  freqs = c (f);  
  while (f> 1) {
    start = 1; #anche provare start = f;
    x = period.apply (x, seq (inizio, lunghezza (x), f), medio); 
    f = find.freq (x);
    freqs = c (freqs, f);
  }
  if (lunghezza (freq) == 1) {return (freq); }
  per (i in 2: lunghezza (freq)) {
    freqs [i] = freqs [i] * freqs [i-1];
  }
  freqs [1: (lunghezza (freqs) -1)];
}
find.freq.all (dailyts) #utilizzando i dati giornalieri

Quanto sopra dà (7,28) o (7,35) a seconda che il seq inizi con 1 o f. (Vedi commento sopra.)

Ciò implicherebbe che i periodi stagionali per msts (...) dovrebbero essere (7,28) o (7,35).

La logica appare sensibile alle condizioni iniziali data la sensibilità dei parametri dell'algoritmo. La media di 28 e 35 è 31,5 che è vicino alla durata media di un mese.

Ho il sospetto di aver reinventato la ruota, come si chiama questo algoritmo? Esiste una migliore implementazione in R da qualche parte?

Più tardi, ho eseguito il codice precedente nel provare tutti gli inizi da 1 a 7 e ho ottenuto 35,35,28,28,28,28,28 per il secondo periodo. La media arriva a 30, che è il numero medio di giorni in un mese. Interessante...

Qualche pensiero o commento?


0

Si può anche usare il test Ljung-Box per capire quale differenza stagionale raggiunge la migliore stazionarietà. Stavo lavorando su un argomento diverso e l'ho usato per gli stessi scopi. Prova diversi periodi come da 3 a 24 per i dati mensili. E testali ciascuno con Ljung-Box e memorizza i risultati di Chi-Square. E scegli il periodo con il valore chi-quadro più basso.

Ecco un semplice codice per farlo.

minval0 <- 5000 #assign a big number to be sure Chi values are smaller
minindex0 <- 0
periyot <- 0

for (i in 3:24) { #find optimum period by Qtests over original data

        d0D1 <- diff(a, lag=i)

        #store results
        Qtest_d0D1[[i]] <- Box.test(d0D1, lag=20, type = "Ljung-Box")

        #store Chi-Square statistics
        sira0[i] <- Qtest_d0D1[[i]][1]
}
#turn list to a data frame, then matrix
datam0 <- data.frame(matrix(unlist(sira0), nrow=length(Qtest_d0D1)-2, byrow = T))
datamtrx0 <- as.matrix(datam0[])
#get min value's index
minindex0 <- which(datamtrx0 == min(datamtrx0), arr.ind = F)
periyot <- minindex0 + 2
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.