Dav*_*ley 8 matlab time-series
我通常需要用给定的聚合函数(即求和,平均等)来总结具有不规则定时的时间序列.但是,我目前的解决方案似乎效率低,速度慢.
采取聚合功能:
function aggArray = aggregate(array, groupIndex, collapseFn)
groups = unique(groupIndex, 'rows');
aggArray = nan(size(groups, 1), size(array, 2));
for iGr = 1:size(groups,1)
grIdx = all(groupIndex == repmat(groups(iGr,:), [size(groupIndex,1), 1]), 2);
for iSer = 1:size(array, 2)
aggArray(iGr,iSer) = collapseFn(array(grIdx,iSer));
end
end
end
Run Code Online (Sandbox Code Playgroud)
请注意这两个array
和groupIndex
可2D.每个列array
都是要聚合的独立系列,但groupIndex
应将这些列合在一起(作为一行)以指定句点.
然后当我们给它带来一个不规则的时间序列时(注意第二个周期是一个基本周期更长),时间结果很差:
a = rand(20006,10);
b = transpose([ones(1,5) 2*ones(1,6) sort(repmat((3:4001), [1 5]))]);
tic; aggregate(a, b, @sum); toc
Elapsed time is 1.370001 seconds.
Run Code Online (Sandbox Code Playgroud)
使用分析器,我们可以发现该grpIdx
行大约占执行时间的1/4(.28秒),iSer
循环大约需要3/4(1.17秒)的总时间(1.48秒).
将此与期间无关紧要的情况相比较:
tic; cumsum(a); toc
Elapsed time is 0.000930 seconds.
Run Code Online (Sandbox Code Playgroud)
有没有更有效的方法来聚合这些数据?
将每个响应放在一个单独的函数中,以下是我timeit
使用Intel i7在Windows 7上使用Matlab 2015b 获得的时序结果:
original | 1.32451
felix1 | 0.35446
felix2 | 0.16432
divakar1 | 0.41905
divakar2 | 0.30509
divakar3 | 0.16738
matthewGunn1 | 0.02678
matthewGunn2 | 0.01977
Run Code Online (Sandbox Code Playgroud)
groupIndex
2D的一个例子groupIndex
是为1980 - 2015年的一组日常数据指定年份编号和周数:
a2 = rand(36*52*5, 10);
b2 = [sort(repmat(1980:2015, [1 52*5]))' repmat(1:52, [1 36*5])'];
Run Code Online (Sandbox Code Playgroud)
因此,"年 - 周"期间由一行唯一标识groupIndex
.这是通过调用unique(groupIndex, 'rows')
和获取第三个输出来有效处理的,所以可以随意忽略这部分问题.
方法#1
您可以创建对应的面具grIdx
在所有
groups
一气呵成用bsxfun(@eq,..)
.现在,collapseFn
因为@sum
,你可以引入matrix-multiplication
,因此有一个完全矢量化的方法,像这样 -
M = squeeze(all(bsxfun(@eq,groupIndex,permute(groups,[3 2 1])),2))
aggArray = M.'*array
Run Code Online (Sandbox Code Playgroud)
对于collapseFn
作为@mean
,你需要做更多的工作,如下所示-
M = squeeze(all(bsxfun(@eq,groupIndex,permute(groups,[3 2 1])),2))
aggArray = bsxfun(@rdivide,M,sum(M,1)).'*array
Run Code Online (Sandbox Code Playgroud)
方法#2
如果您正在使用泛型collapseFn
,则可以使用使用M
先前方法创建的2D掩码来索引行array
,从而将复杂度从更改O(n^2)
为O(n)
.一些快速测试表明,这可以比原始的loopy代码提供明显的加速.这是实施 -
n = size(groups,1);
M = squeeze(all(bsxfun(@eq,groupIndex,permute(groups,[3 2 1])),2));
out = zeros(n,size(array,2));
for iGr = 1:n
out(iGr,:) = collapseFn(array(M(:,iGr),:),1);
end
Run Code Online (Sandbox Code Playgroud)
请注意,1
in collapseFn(array(M(:,iGr),:),1)
表示collapseFn
将应用的尺寸,因此在1
那里必不可少.
奖金
通过它的名称groupIndex
似乎可以保存整数值,通过将每一行视为索引元组并因此将每行转换为标量并最终获得1D数组版本,可以滥用它来更高效地M
创建.这必须更高效,因为现在数据量.这可以用于本文中列出的所有方法.所以,我们会这样 -groupIndex
groupIndex
groupIndex
0(n)
M
M
dims = max(groupIndex,[],1);
agg_dims = cumprod([1 dims(end:-1:2)]);
[~,~,idx] = unique(groupIndex*agg_dims(end:-1:1).'); %//'
m = size(groupIndex,1);
M = false(m,max(idx));
M((idx-1)*m + [1:m]') = 1;
Run Code Online (Sandbox Code Playgroud)
HAMMER TIME:用于粉碎它的Mex功能:使用问题原始代码的基本案例测试在我的机器上花了1.334139秒.恕我直言,来自@Divakar的第二快答案是:
groups2 = unique(groupIndex);
aggArray2 = squeeze(all(bsxfun(@eq,groupIndex,permute(groups,[3 2 1])),2)).'*array;
Run Code Online (Sandbox Code Playgroud)
经过的时间是0.589330秒.
那我的MEX功能:
[groups3, aggArray3] = mg_aggregate(array, groupIndex, @(x) sum(x, 1));
Run Code Online (Sandbox Code Playgroud)
经过时间为0.079725秒.
测试我们得到相同的答案:norm(groups2-groups3)
返回0
和norm(aggArray2 - aggArray3)
返回2.3959e-15
.结果也符合原始代码.
用于生成测试条件的代码:
array = rand(20006,10);
groupIndex = transpose([ones(1,5) 2*ones(1,6) sort(repmat((3:4001), [1 5]))]);
Run Code Online (Sandbox Code Playgroud)
对于纯粹的速度,去mex.如果编译c ++代码/复杂性的想法太痛苦了,那就去看Divakar的答案吧.另一个免责声明:我没有对我的功能进行强有力的测试.
有点令我惊讶的是,在某些情况下,此代码看起来比完整的Mex版本更快(例如,在此测试中花费约0.05秒).它使用mex函数mg_getRowsWithKey来计算组的索引.我想这可能是因为我在完整的mex函数中复制的数组并不像调用'feval'那样快和/或开销.它与其他版本的算法复杂度基本相同.
[unique_groups, map] = mg_getRowsWithKey(groupIndex);
results = zeros(length(unique_groups), size(array,2));
for iGr = 1:length(unique_groups)
array_subset = array(map{iGr},:);
%// do your collapse function on array_subset. eg.
results(iGr,:) = sum(array_subset, 1);
end
Run Code Online (Sandbox Code Playgroud)
当您array(groups(1)==groupIndex,:)
提取与完整组关联的数组条目时,您正在搜索groupIndex的整个长度.如果你有数百万行输入,这将完全糟糕.array(map{1},:)
效率更高.
仍有不必要的内存复制和与在崩溃函数上调用'feval'相关的其他开销.如果您在c ++中有效地实现聚合器功能以避免复制内存,则可能实现另外2倍的加速.
聚会晚了一点,但是使用一个循环accumarray
就产生了很大的不同:
function aggArray = aggregate_gnovice(array, groupIndex, collapseFn)
[groups, ~, index] = unique(groupIndex, 'rows');
numCols = size(array, 2);
aggArray = nan(numel(groups), numCols);
for col = 1:numCols
aggArray(:, col) = accumarray(index, array(:, col), [], collapseFn);
end
end
Run Code Online (Sandbox Code Playgroud)
使用timeit
MATLAB R2016b中的时间对此问题中的示例数据进行计时:
original | 1.127141
gnovice | 0.002205
Run Code Online (Sandbox Code Playgroud)
加速超过500倍!