Періодичне виявлення загального часового ряду


53

Цей пост є продовженням іншої публікації, пов’язаної із загальним методом виявлення зовнішньої хронології у часових рядах . В основному, на даний момент мене цікавить надійний спосіб виявити періодичність / сезонність загальних часових рядів, на які впливає багато шуму. З точки зору розробника, я хотів би простий інтерфейс, такий як:

unsigned int discover_period(vector<double> v);

Де vзнаходиться масив, що містить вибірки, а значення повернення - період сигналу. Основний момент полягає в тому, що, знову ж таки, я не можу зробити жодного припущення щодо аналізованого сигналу. Я вже спробував підхід, заснований на автокореляції сигналу (виявлення піків корелограми), але це не надійно, як хотілося б.


1
Ви пробували xts :: періодичність?
Фабріціо

Відповіді:


49

Якщо ви справді не знаєте, що таке періодичність, можливо, найкращим підходом є пошук частоти, що відповідає максимуму спектральної щільності. Однак на спектр низьких частот буде впливати тенденція, тому вам потрібно спочатку зменшити серію. Наступна функція R повинна виконати роботу для більшості серій. Це далеко не ідеально, але я перевірив це на кількох десятках прикладів, і, здається, працює нормально. Він поверне 1 для даних, які не мають сильної періодичності, а тривалість періоду в іншому випадку.

Оновлення: Версія 2 функції. Це набагато швидше і здається більш надійним.

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

Дякую. Знову спробую такий підхід якомога швидше і напишу тут кінцеві результати.
gianluca

2
Ваша ідея досить гарна, але в моєму випадку вона не вдається виявити періодичність дійсно простого (і не настільки галасливого) часового ряду, як dl.dropbox.com/u/540394/chart.png . З мого "емпіричного" підходу (заснованого на автокореляції) простий алгоритм, який я написав, повертає точний період 1008 (маючи вибірку кожні 10 хвилин, це означає 1008/24/6 = 7, тобто періодичність періодичності). Мої основні проблеми: 1) занадто повільно конвергуватися (для цього потрібно багато історичних даних), і мені потрібен реактивний підхід в Інтернеті; 2) з точки зору використання пам'яті це неефективно; 3) Це зовсім не надійно;
gianluca

Дякую. На жаль, це все ще не працює, як я очікував. За той же часовий ряд попереднього коментаря він повертає 166, що лише частково вірно (з моєї точки зору, очевидний тижневий період цікавіший). І використовуючи дуже шумний часовий ряд, як-от цей dl.dropbox.com/u/540394/chart2.png (аналіз вікна приймача TCP), функція повертає 10, тоді як я очікував 1 (я не бачу очевидних періодичність). До речі, я знаю, що знайти те, що я шукаю, буде дуже важко, оскільки я маю справу з надто різними сигналами.
gianluca

166 - це не погана оцінка 168. Якщо ви знаєте, що дані спостерігаються щогодини за тижневою схемою, то навіщо взагалі оцінювати частоту?
Роб Хайндман

5
Удосконалена версія є в пакеті прогнозів якfindfrequency
Роб Хайндман

10

Якщо ви очікуєте, що процес буде стаціонарним - періодичність / сезонність не зміниться з часом - тоді щось на зразок періодограми Chi-квадрата (див., Наприклад, Sokolove and Bushell, 1978) може бути хорошим вибором. Він зазвичай використовується для аналізу циркадних даних, які можуть мати надзвичайно велику кількість шуму в ньому, але, як очікується, мають дуже стійкі періодичні періоди.

Цей підхід не припускає форми форми хвилі (крім того, що вона відповідає циклу до циклу), але вимагає, щоб будь-який шум був постійним середнім і не співвідносився з сигналом.

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)

Останні два рядки - лише приклад, що показує, що він може визначити період чистої тригонометричної функції, навіть з великою кількістю аддитивного шуму.

Як написано, останній аргумент ( alpha) у виклику зайвий, функція просто повертає найкращий період, який він може знайти; returnкоментуйте перше твердження та прокоментуйте друге, щоб воно повернуло список усіх періодів, важливих на рівні alpha.

Ця функція не проводить ніякої перевірки обгрунтованості, щоб переконатися, що ви ввели ідентифіковані періоди, а також не може (вона може) працювати з дробовими періодами, а також не вбудований численний контроль порівняння, якщо ви вирішите подивіться на кілька періодів. Але крім цього він повинен бути досить надійним.


Виглядає цікаво, але я не розумію вихід, він не підказує, звідки починається період, і більшість значень 1.
Зубний Герман

3

Можливо, ви хочете чіткіше визначити, що ви хочете (якщо не тут). Якщо ви шукаєте найбільш статистично важливий стаціонарний період, який міститься у ваших галасливих даних, то, по суті, слід скористатися двома маршрутами:

1) обчислити надійну оцінку автокореляції та взяти максимальний коефіцієнт
2) обчислити надійну оцінку спектральної щільності потужності та взяти максимум спектра

Проблема №2 полягає в тому, що за будь-який галасливий часовий ряд ви отримаєте велику кількість енергії на низьких частотах, що ускладнює їх розрізнення. Існують деякі методи вирішення цієї проблеми (наприклад, попередньо відбілити, а потім оцінити PSD), але якщо справжній період з ваших даних буде досить довгим, автоматичне виявлення буде непростим.

Ваша найкраща ставка - це, мабуть, реалізація надійної процедури автокореляції, таку, яку можна знайти у розділі 8.6, 8.7 в " Надійна статистика - теорія та методи " Маронни, Мартіна та Йохая. Пошук у Google "надійного дурбіна-левінсона" також дасть певні результати.

Якщо ви просто шукаєте просту відповідь, я не впевнений, що така існує. Виявлення періоду у часових рядах може бути складним, і запитувати автоматизовану процедуру, яка може виконувати магію, може бути занадто багато.


Дякую за дорогоцінну інформацію, я точно перегляну цю книгу.
gianluca

3

Ви можете використовувати трансформацію Гільберта з теорії DSP для вимірювання миттєвої частоти даних. На сайті http://ta-lib.org/ є відкритий код для вимірювання домінуючого періоду фінансових даних; відповідна функція називається HT_DCPERIOD; ви можете використовувати це або адаптувати код до своїх цілей.


3

Іншим підходом може бути декомпозиція емпіричного режиму. Пакет R називається EMD, розроблений винахідником способу:

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

Метод отримав назву "Емпіричний" з поважної причини, і існує ризик, що функції внутрішнього режиму (окремі компоненти добавок) змішаться. З іншого боку, метод є дуже інтуїтивним і може бути корисним для швидкого візуального контролю циклічності.


0

Посилаючись на пост Роба Хайндмана вище https://stats.stackexchange.com/a/1214/70282

Функція find.freq працює чудово. У щоденному наборі даних, який я використовую, він правильно розробив частоту 7

Коли я спробував це лише у дні тижня, він згадав про частоту 23, що надзвичайно близько до 21.42857 = 29.6 * 5/7, що є середньою кількістю робочих днів у місяці. (Або навпаки 23 * 7/5 - 32.)

Озираючись на свої щоденні дані, я експериментував із набором першого періоду, усереднюючи його, а потім знаходив наступний період тощо. Дивіться нижче:

find.freq.all = функція (x) {  
  f = find.freq (x);
  freqs = c (f);  
  while (f> 1) {
    старт = 1; # також спробувати start = f;
    x = period.apply (x, seq (початок, довжина (x), f), mean); 
    f = find.freq (x);
    freqs = c (freqs, f);
  }
  if (length (freqs) == 1) {return (freqs); }
  для (i in 2: length (freqs)) {
    freqs [i] = freqs [i] * freqs [i-1];
  }
  freqs [1: (довжина (freqs) -1)];
}
find.freq.all (dailyts) # використання щоденних даних

Вищенаведене дає (7,28) або (7,35) залежно від того, чи починається послідовність з 1 або f. (Див. Коментар вище.)

Що означає, що сезонні періоди для msts (...) повинні бути (7,28) або (7,35).

Логіка видається чутливою до початкових умов, враховуючи чутливість параметрів алгоритму. Середнє значення 28 і 35 становить 31,5, що близько до середньої тривалості місяця.

Я підозрюю, що я винаходив колесо, як називається цей алгоритм? Чи є десь краща реалізація в R?

Пізніше я застосував вищевказаний код, намагаючись усі старти з 1 по 7 і отримав 35,35,28,28,28,28,28 за другий період. В середньому працює до 30, що є середньою кількістю днів у місяці. Цікаво ...

Будь-які думки чи коментарі?


0

Можна також використати тест Ljung-Box, щоб визначити, яка різниця в сезоні досягає найкращої стаціонарності. Я працював над іншою темою, і використовував це фактично для тих же цілей. Спробуйте різні періоди, наприклад, від 3 до 24 для щомісячних даних. І протестуйте кожен з них за допомогою Ljung-Box і зберігайте результати Chi-Square. І вибирайте період з найнижчим значенням квадратних чи.

Ось простий код для цього.

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.