Periodenerkennung einer generischen Zeitreihe

53

Dieser Beitrag ist die Fortsetzung eines anderen Beitrags, der sich auf eine allgemeine Methode zur Erkennung von Ausreißern in Zeitreihen bezieht . Grundsätzlich bin ich an dieser Stelle an einer robusten Methode interessiert, um die Periodizität / Saisonalität einer allgemeinen Zeitreihe zu ermitteln, die von vielen Störungen betroffen ist. Aus Entwicklersicht hätte ich gerne eine einfache Oberfläche wie:

unsigned int discover_period(vector<double> v);

Wo vist das Array mit den Samples und der Rückgabewert ist die Periode des Signals. Das Wichtigste ist, dass ich auch in Bezug auf das analysierte Signal keine Vermutung anstellen kann. Ich habe bereits einen Ansatz ausprobiert, der auf der Autokorrelation des Signals basiert (Erkennen der Peaks eines Korrelogramms), aber er ist nicht so robust, wie ich es gerne hätte.

gianluca
quelle
1
Haben Sie xts :: periodicity ausprobiert?
Fabrício

Antworten:

49

Wenn Sie wirklich keine Ahnung haben, wie hoch die Periodizität ist, ist es wahrscheinlich am besten, die Frequenz zu finden, die dem Maximum der spektralen Dichte entspricht. Das Spektrum bei niedrigen Frequenzen wird jedoch vom Trend beeinflusst, daher müssen Sie zuerst die Serie abwerten. Die folgende R-Funktion sollte für die meisten Serien den Job erledigen. Es ist alles andere als perfekt, aber ich habe es an ein paar Dutzend Beispielen getestet und es scheint in Ordnung zu funktionieren. Es wird 1 für Daten zurückgegeben, die keine starke Periodizität aufweisen, andernfalls für die Länge des Zeitraums.

Update: Version 2 der Funktion. Dies ist viel schneller und scheint robuster zu sein.

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)
}
Rob Hyndman
quelle
Danke. Ich werde diesen Ansatz so bald wie möglich ausprobieren und hier die endgültigen Ergebnisse schreiben.
Gianluca
2
Ihre Idee ist ganz gut, aber in meinem Fall erkennt sie nicht die Periodizität einer wirklich einfachen (und nicht so verrauschten) Zeitreihe wie dl.dropbox.com/u/540394/chart.png . Mit meinem "empirischen" Ansatz (basierend auf der Autokorrelation) gibt der einfache Algorithmus, den ich geschrieben habe, eine exakte Periode von 1008 zurück (mit einer Stichprobe alle 10 Minuten, dh 1008/24/6 = 7, also eine wöchentliche Periodizität). Meine Hauptprobleme sind: 1) Es ist zu langsam für die Konvergenz (es erfordert viele historische Daten) und ich brauche einen reaktiven Online-Ansatz; 2) Unter dem Gesichtspunkt der Speichernutzung ist es absolut ineffizient. 3) Es ist überhaupt nicht robust;
Gianluca
Danke. Leider funktioniert das immer noch nicht so, wie ich es erwartet hätte. Für die gleiche Zeitreihe des vorherigen Kommentars wird 166 zurückgegeben, was nur teilweise richtig ist (aus meiner Sicht ist die offensichtliche Wochenperiode interessanter). Unter Verwendung einer sehr verrauschten Zeitreihe, wie dieser dl.dropbox.com/u/540394/chart2.png (eine TCP-Empfänger-Fensteranalyse), gibt die Funktion 10 zurück, während ich 1 erwarten würde (ich sehe keine offensichtliche) Periodizität). Übrigens weiß ich, dass es sehr schwierig sein wird, das zu finden, wonach ich suche, da ich mit zu unterschiedlichen Signalen zu tun habe.
Gianluca
166 ist keine schlechte Schätzung von 168. Wenn Sie wissen, dass die Daten stündlich mit einem wöchentlichen Muster beobachtet werden, warum dann die Häufigkeit überhaupt schätzen?
Rob Hyndman
5
Eine verbesserte Version ist im Vorhersagepaket alsfindfrequency
Rob Hyndman
10

Wenn Sie erwarten, dass der Prozess stationär ist - die Periodizität / Saisonalität wird sich mit der Zeit nicht ändern -, ist möglicherweise so etwas wie ein Chi-Quadrat-Periodogramm (siehe z. B. Sokolove und Bushell, 1978) eine gute Wahl. Es wird üblicherweise zur Analyse von circadianen Daten verwendet, die extrem viel Rauschen enthalten können, von denen jedoch sehr stabile Periodizitäten erwartet werden.

Bei diesem Ansatz wird keine Annahme über die Form der Wellenform getroffen (abgesehen davon, dass sie von Zyklus zu Zyklus konsistent ist), jedoch muss jedes Rauschen einen konstanten Mittelwert haben und nicht mit dem Signal korreliert sein.

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)

Die letzten beiden Zeilen sind nur ein Beispiel, das zeigt, dass die Periode einer reinen trigonometrischen Funktion auch mit viel additivem Rauschen identifiziert werden kann.

Wie geschrieben, ist das letzte Argument ( alpha) im Aufruf überflüssig. Die Funktion gibt einfach die 'beste' Periode zurück, die sie finden kann. returnKommentieren Sie die erste Anweisung aus, und kommentieren Sie die zweite aus, um eine Liste aller auf der Ebene signifikanten Zeiträume zurückzugeben alpha.

Diese Funktion prüft nicht, ob Sie identifizierbare Zeiträume eingegeben haben. Sie kann auch nicht mit Bruchperioden arbeiten. Bei Bedarf ist auch keine Mehrfachvergleichssteuerung integriert Schauen Sie sich mehrere Perioden an. Aber ansonsten sollte es einigermaßen robust sein.

Reich
quelle
Sieht interessant aus, aber ich verstehe die Ausgabe nicht, es sagt mir nicht, wo die Periode beginnt, und die meisten p-Werte von 1.
Herman Toothrot
3

Möglicherweise möchten Sie klarer definieren, was Sie möchten (für sich selbst, wenn nicht hier). Wenn Sie nach der statistisch signifikantesten stationären Periode suchen, die in Ihren verrauschten Daten enthalten ist, müssen Sie im Wesentlichen zwei Routen wählen:

1) Berechnen Sie eine robuste Autokorrelationsschätzung und nehmen Sie den maximalen Koeffizienten
2) Berechnen Sie eine robuste Leistungsspektraldichteschätzung und nehmen Sie das Maximum des Spektrums

Das Problem bei Nr. 2 ist, dass Sie für alle verrauschten Zeitreihen eine große Leistung bei niedrigen Frequenzen erhalten, was die Unterscheidung erschwert. Es gibt einige Techniken, um dieses Problem zu lösen (z. B. Vorbleichen und dann Schätzen der PSD). Wenn der wahre Zeitraum Ihrer Daten jedoch lang genug ist, ist die automatische Erkennung problematisch.

Am besten ist es wahrscheinlich, eine robuste Autokorrelationsroutine zu implementieren, wie sie in Kapitel 8.6, 8.7 in Theorie und Methoden der robusten Statistik von Maronna, Martin und Yohai zu finden ist. Die Suche bei Google nach "robustem Durbin-Levinson" wird ebenfalls zu einigen Ergebnissen führen.

Wenn Sie nur nach einer einfachen Antwort suchen, bin ich mir nicht sicher, ob es eine gibt. Die Periodenerkennung in Zeitreihen kann kompliziert sein, und es kann zu viel sein, nach einer automatisierten Routine zu fragen, die Magie ausführen kann.

Wesley Burr
quelle
Vielen Dank für Ihre wertvollen Informationen, ich werde dieses Buch auf jeden Fall anschauen.
Gianluca
3

Sie können die Hilbert-Transformation aus der DSP-Theorie verwenden, um die Momentanfrequenz Ihrer Daten zu messen. Die Website http://ta-lib.org/ enthält Open-Source-Code zur Messung der dominanten Zykluszeit von Finanzdaten. die relevante Funktion heißt HT_DCPERIOD; Sie können dies möglicherweise verwenden oder den Code an Ihre Zwecke anpassen.

babelproofreader
quelle
3

Ein anderer Ansatz könnte die empirische Moduszerlegung sein. Das R-Paket heißt EMD und wurde vom Erfinder des Verfahrens entwickelt:

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

Die Methode wurde aus gutem Grund als "empirisch" eingestuft, und es besteht die Gefahr, dass die intrinsischen Modusfunktionen (die einzelnen additiven Komponenten) vertauscht werden. Andererseits ist die Methode sehr intuitiv und kann für eine schnelle visuelle Überprüfung der Zyklizität hilfreich sein.

Fabrizio Maccallini
quelle
0

In Bezug auf Rob Hyndmans Beitrag über https://stats.stackexchange.com/a/1214/70282

Die find.freq-Funktion funktioniert hervorragend. Auf dem täglichen Datensatz, den ich verwende, wurde die Häufigkeit mit 7 korrekt berechnet.

Als ich es nur an den Wochentagen ausprobierte, wurde erwähnt, dass die Häufigkeit 23 ist, was bemerkenswert nahe an 21,42857 = 29,6 * 5/7 liegt, was der durchschnittlichen Anzahl von Arbeitstagen pro Monat entspricht. (Oder umgekehrt 23 * 7/5 ist 32.)

Wenn ich auf meine täglichen Daten zurückblicke, experimentierte ich mit der Vermutung, dass ich die erste Periode genommen, daraus gemittelt und dann die nächste Periode gefunden habe, usw. Siehe unten:

find.freq.all = function (x) {  
  f = find.freq (x);
  freqs = c (f);  
  während (f> 1) {
    start = 1; #versuche auch start = f;
    x = period.apply (x, seq (Anfang, Länge (x), f), Mittelwert); 
    f = find.freq (x);
    freqs = c (freqs, f);
  }
  if (length (freqs) == 1) {return (freqs); }
  für (i in 2: Länge (Freqs)) {
    freqs [i] = freqs [i] * freqs [i-1];
  }
  Frequenzen [1: (Länge (Frequenzen) -1)];
}
find.freq.all (dailyts) #mit täglichen Daten

Das obige ergibt (7,28) oder (7,35), je nachdem, ob die Folge mit 1 oder f beginnt. (Siehe Kommentar oben.)

Was bedeuten würde, dass die saisonalen Perioden für MSTs (...) (7,28) oder (7,35) sein sollten.

Die Logik scheint in Anbetracht der Empfindlichkeit der Algorithmusparameter empfindlich gegenüber Anfangsbedingungen zu sein. Der Mittelwert von 28 und 35 liegt bei 31,5, was in etwa der durchschnittlichen Länge eines Monats entspricht.

Ich vermute, ich habe das Rad neu erfunden. Wie heißt dieser Algorithmus? Gibt es irgendwo eine bessere Implementierung in R?

Später habe ich den obigen Code ausgeführt, indem ich alle Starts von 1 bis 7 ausprobiert habe, und ich habe 35,35,28,28,28,28,28,28 für die zweite Periode erhalten. Der Durchschnitt liegt bei 30 Tagen pro Monat. Interessant...

Irgendwelche Gedanken oder Kommentare?

Chris
quelle
0

Mit dem Ljung-Box-Test kann man auch herausfinden, welcher saisonale Unterschied die beste Stationarität erreicht. Ich habe an einem anderen Thema gearbeitet und es tatsächlich für die gleichen Zwecke verwendet. Probieren Sie verschiedene Zeiträume wie 3 bis 24 aus, um monatliche Daten zu erhalten. Und testen Sie jeden von ihnen mit der Ljung-Box und speichern Sie die Chi-Square-Ergebnisse. Und wählen Sie die Periode mit dem niedrigsten Chi-Quadrat-Wert.

Hier ist ein einfacher Code, um das zu tun.

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
ali
quelle