Обнаружение периода общего временного ряда


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

Спасибо. Опять же, я попробую этот подход как можно скорее и напишу здесь окончательные результаты.
Джанлука

2
Ваша идея довольно хороша, но в моем случае она не может определить периодичность действительно простых (и не очень шумных) временных рядов, таких как dl.dropbox.com/u/540394/chart.png . С моим «эмпирическим» подходом (основанным на автокорреляции), простой алгоритм, который я написал, возвращает точный период 1008 (выборка каждые 10 минут, это означает 1008/24/6 = 7, то есть еженедельная периодичность). Мои основные проблемы: 1) Слишком медленное сближение (требует много исторических данных), и мне нужен реактивный онлайн-подход; 2) Это чертовски неэффективно с точки зрения использования памяти; 3) это совсем не надёжно;
Джанлука

Спасибо. К сожалению, это все еще не работает, как я ожидал. Для того же временного ряда предыдущего комментария он возвращает 166, что является лишь частично правильным (с моей точки зрения, очевидный недельный период более интересен). И используя очень шумные временные ряды, такие как этот dl.dropbox.com/u/540394/chart2.png (анализ окна получателя TCP), функция возвращает 10, в то время как я ожидаю 1 (я не вижу очевидного периодичность). Кстати, я знаю, что будет действительно трудно найти то, что я ищу, так как я имею дело со слишком разными сигналами.
Джанлука

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

5
Улучшенная версия в пакете прогноза, какfindfrequency
Роб Хиндман

10

Если вы ожидаете, что процесс будет стационарным - периодичность / сезонность не изменится со временем - тогда что-то вроде периодограммы хи-квадрат (см., Например, 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.
Herman

3

Вы можете определить, что вы хотите более четко (для себя, если не здесь). Если то, что вы ищете, является наиболее статистически значимым стационарным периодом, содержащимся в ваших зашумленных данных, по сути, есть два пути:

1) вычислить надежную оценку автокорреляции и взять максимальный коэффициент
2) вычислить надежную оценку спектральной плотности мощности и взять максимум спектра

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

Лучше всего, вероятно, реализовать надежную процедуру автокорреляции, такую ​​как можно найти в главе 8.6, 8.7 в Робастная статистика - теория и методы Маронны, Мартина и Йохая. Поиск в Google по запросу "надежный Дурбин-Левинсон" также даст некоторые результаты.

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


Спасибо за вашу ценную информацию, я обязательно посмотрю эту книгу.
Джанлука

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 = функция (х) {  
  е = find.freq (х);
  freqs = C (F);  
  в то время как (е> 1) {
    начать = 1; # также попробуйте start = f;
    х = period.apply (х, след (начало, длина (х), е), средний); 
    е = find.freq (х);
    freqs = C (freqs, F);
  }
  if (length (freqs) == 1) {return (freqs); }
  для (я в 2: длина (частоты)) {
    freqs [I] = freqs [я] * freqs [I-1];
  }
  freqs [1: (длина (freqs) -1)];
}
find.freq.all (dailyts) # использование ежедневных данных

Выше приведены (7,28) или (7,35) в зависимости от того, начинается ли seq с 1 или f. (См. Комментарий выше.)

Что подразумевает, что сезонные периоды для msts (...) должны быть (7,28) или (7,35).

Логика кажется чувствительной к начальным условиям, учитывая чувствительность параметров алгоритма. Среднее значение 28 и 35 составляет 31,5, что близко к средней продолжительности месяца.

Я подозреваю, что я заново изобрел колесо, как называется этот алгоритм? Есть ли лучшая реализация в R где-нибудь?

Позже я запустил приведенный выше код, попробовав все запуски с 1 по 7, и получил 35,35,28,28,28,28,28 за второй период. В среднем получается до 30, что является средним числом дней в месяце. Интересно...

Есть мысли или комментарии?


0

Можно также использовать тест Юнга-Бокса, чтобы выяснить, какая сезонная разница достигает наилучшей стационарности. Я работал над другим предметом, и я использовал это на самом деле для тех же целей. Попробуйте разные периоды, например от 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.