MATLAB: вычислить среднее значение каждого 1-минутного интервала временного ряда

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

Я пытаюсь вычислить среднее / SD каждого 1-минутного интервала значений (возьмите X минутный интервал, вычислите его среднее значение, возьмите следующий интервал, ...).

моя текущая реализация использует петли. Это образец того, что у меня есть. далеко:

t = (100:999)' + rand(900,1);       %' non-uniform time
x = 5*rand(900,1) + 10;             % x(i) is the value at time t(i)

interval = 1;         % 1-min interval
tt = ( floor(t(1)):interval*60:ceil(t(end)) )';  %' stopping points of each interval
N = length(tt)-1;

mu = zeros(N,1);
sd = zeros(N,1);

for i=1:N
    indices = ( tt(i) <= t & t < tt(i+1) ); % find t between tt(i) and tt(i+1)
    mu(i) = mean( x(indices) );
    sd(i) = std( x(indices) );
end

мне интересно, есть ли более быстрое векторизованное решение. Это важно, потому что у меня есть большое количество временных рядов для обработки каждого гораздо дольше, чем на рисунке выше..

любая помощь приветствуется.


спасибо всем за отзывы.

я исправил путь t генерируется, чтобы всегда монотонно увеличиваться (сортироваться), это не было проблемой..

кроме того, я, возможно, не заявил об этом ясно, но мое намерение состояло в том, чтобы иметь решение для любой длины интервала в минутах (1-min был просто примером)

6 ответов


кажется, единственное логическое решение...

Ok. Мне смешно, что для меня есть только одно логическое решение, но многие другие находят другие решения. Несмотря на это, решение кажется простым. Учитывая векторы x и t и множество равноудаленных точек разрыва tt,

t = sort((100:999)' + 3*rand(900,1));     % non-uniform time
x = 5*rand(900,1) + 10;             % x(i) is the value at time t(i)

tt = ( floor(t(1)):1*60:ceil(t(end)) )';

(обратите внимание, что я отсортировал t выше.)

Я бы сделал это в трех полностью векторизованных строках кода. Во-первых, если разрывы были произвольными и потенциально неравные интервалы, я будет использовать histc для определения интервалов, в которые попадает ряд данных. Учитывая, что они однородны, просто сделайте следующее:

int = 1 + floor((t - t(1))/60);

опять же, если бы не было известно, что элементы t сортируются, я бы использовал min(t) вместо t (1). Сделав это, используйте accumarray для уменьшения результатов до среднего и стандартного отклонения.

mu = accumarray(int,x,[],@mean);
sd = accumarray(int,x,[],@std);

вы можете попробовать создать массив ячеек и применить mean и std через cellfun. Это ~10% медленнее, чем ваше решение для 900 записей, но ~10x быстрее для 90000 записей.

[t,sortIdx]=sort(t); %# we only need to sort in case t is not monotonously increasing
x = x(sortIdx);

tIdx = floor(t/60); %# convert seconds to minutes - can also convert to 5 mins by dividing by 300
tIdx = tIdx - min(tIdx) + 1; %# tIdx now is a vector of indices - i.e. it starts at 1, and should go like your iteration variable.

%# the next few commands are to count how many 1's 2's 3's etc are in tIdx
dt = [tIdx(2:end)-tIdx(1:end-1);1]; 
stepIdx = [0;find(dt>0)];
nIdx = stepIdx(2:end) - stepIdx(1:end-1); %# number of times each index appears

%# convert to cell array
xCell = mat2cell(x,nIdx,1);

%# use cellfun to calculate the mean and sd
mu(tIdx(stepIdx+1)) = cellfun(@mean,xCell); %# the indexing is like that since there may be missing steps
sd(tIdx(stepIdx+1)) = cellfun(@mean,xCell);

Примечание: мое решение не дает тех же результатов, что и ваше, так как вы пропускаете несколько значений времени в конце (1:60:90 - [1,61]), и так как начало интервала не совсем то же самое.


вот способ, который использует бинарный поиск. Это 6-10x быстрее для 9900 элементов и примерно в 64 раза быстрее для 99900 элементов. Было трудно получить надежные времена, используя только 900 элементов, поэтому я не уверен, что быстрее в этом размере. Он не использует почти никакой дополнительной памяти, если вы рассматриваете создание tx непосредственно из сгенерированных данных. Кроме этого, он имеет только четыре дополнительные переменные float (prevind, first, mid и last).

% Sort the data so that we can use binary search (takes O(N logN) time complexity).
tx = sortrows([t x]);

prevind = 1;

for i=1:N
    % First do a binary search to find the end of this section
    first = prevind;
    last = length(tx);
    while first ~= last
        mid = floor((first+last)/2);
        if tt(i+1) > tx(mid,1)
            first = mid+1;
        else
            last = mid;
        end;
    end;
    mu(i) = mean( tx(prevind:last-1,2) );
    sd(i) = std( tx(prevind:last-1,2) );
    prevind = last;
end;

Он использует все переменные, которые вы изначально. Я надеюсь, что это соответствует вашим потребностям. Это быстрее, потому что требуется O(log N), чтобы найти индексы с двоичным поиском, но O(N), чтобы найти их так, как вы это делали.


можно вычислить indices все сразу с помощью bsxfun:

indices = ( bsxfun(@ge, t, tt(1:end-1)') & bsxfun(@lt, t, tt(2:end)') );

это быстрее, чем цикл, но требует хранения их всех сразу (время против пространства компромисса)..


отказ от ответственности: я разработал это на бумаге, но еще не имел возможности проверить его "в силико"...

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

[t,sortIndex] = sort(t);  %# Sort the time points
x = x(sortIndex);         %# Sort the data values
interval = 60;            %# Interval size, in seconds

intervalIndex = floor((t-t(1))./interval)+1;  %# Collect t into intervals
nIntervals = max(intervalIndex);              %# The number of intervals
mu = zeros(nIntervals,1);                     %# Preallocate mu
sd = zeros(nIntervals,1);                     %# Preallocate sd

sumIndex = [find(diff(intervalIndex)) ...
            numel(intervalIndex)];  %# Find indices of the interval ends
n = diff([0 sumIndex]);             %# Number of samples per interval
xSum = cumsum(x);                   %# Cumulative sum of x
xSum = diff([0 xSum(sumIndex)]);    %# Sum per interval
xxSum = cumsum(x.^2);               %# Cumulative sum of x^2
xxSum = diff([0 xxSum(sumIndex)]);  %# Squared sum per interval

intervalIndex = intervalIndex(sumIndex);  %# Find index into mu and sd
mu(intervalIndex) = xSum./n;                             %# Compute mean
sd(intervalIndex) = sqrt((xxSum-xSum.*xSum./n)./(n-1));  %# Compute std dev

выше вычисляет стандартное отклонение, используя упрощение формулы, найденной на этой странице Википедии.


тот же ответ, что и выше, но с параметрическим интервалом (window_size). Проблема с длиной вектора также решена.

window_size = 60; % but it can be any value 60 5 0.1, which wasn't described above

t = sort((100:999)' + 3*rand(900,1));     % non-uniform time
x = 5*rand(900,1) + 10;                   % x(i) is the value at time t(i)

int = 1 + floor((t - t(1))/window_size);
tt = ( floor(t(1)):window_size:ceil(t(end)) )';



% mean val and std dev of the accelerations at speed
mu = accumarray(int,x,[],@mean);
sd = accumarray(int,x,[],@std);

%resolving some issue with sizes (for i.e. window_size = 1 in stead of 60)
while ( sum(size(tt) > size(mu)) > 0 ) 
  tt(end)=[]; 
end

errorbar(tt,mu,sd);