2

2つのアレイがあります。1つは、もう1つの長さのリストです。例えば

zarray = [1 2 3 4 5 6 7 8 9 10]

lengths = [1 3 2 1 3]

最初の配列を2番目の配列で指定された長さで部分的に平均(平均)したいと思います。この例では、次のようになります。

[mean([1]),mean([2,3,4]),mean([5,6]),mean([7]),mean([8,9,10])]

速度を上げるために、ループを避けようとしています。mat2cellとcellfunを次のように使ってみました

zcell = mat2cell(zarray,[1],lengths);
zcellsum = cellfun('mean',zcell);

しかし、cellfunの部分は非常に遅いです。ループやcellfunなしでこれを行う方法はありますか?

4

2 に答える 2

2

これは完全にベクトル化されたソリューションです(明示的なforループ、またはARRAYFUN、CELLFUNなどの非表示ループはありません)。アイデアは、非常に高速なACCUMARRAY関数を使用することです。

%# data
zarray = [1 2 3 4 5 6 7 8 9 10];
lengths = [1 3 2 1 3];

%# generate subscripts: 1 2 2 2 3 3 4 5 5 5
endLocs = cumsum(lengths(:));
subs = zeros(endLocs(end),1);
subs([1;endLocs(1:end-1)+1]) = 1;
subs = cumsum(subs);

%# mean of each part
means = accumarray(subs, zarray) ./ lengths(:)

この場合の結果:

means =
            1
            3
          5.5
            7
            9

スピードテスト:

さまざまな方法の次の比較を検討してください。私はSteveEddinsによるTIMEIT関数を使用します:

function [t,v] = testMeans()
    %# generate test data
    [arr,len] = genData();

    %# define functions
    f1 = @() func1(arr,len);
    f2 = @() func2(arr,len);
    f3 = @() func3(arr,len);
    f4 = @() func4(arr,len);

    %# timeit
    t(1) = timeit( f1 );
    t(2) = timeit( f2 );
    t(3) = timeit( f3 );
    t(4) = timeit( f4 );

    %# return results to check their validity
    v{1} = f1();
    v{2} = f2();
    v{3} = f3();
    v{4} = f4();
end

function [arr,len] = genData()
    %#arr = [1 2 3 4 5 6 7 8 9 10];
    %#len = [1 3 2 1 3];

    numArr = 10000;     %# number of elements in array
    numParts = 500;     %# number of parts/regions      
    arr = rand(1,numArr);
    len = zeros(1,numParts);
    len(1:end-1) = diff(sort( randperm(numArr,numParts) ));
    len(end) = numArr - sum(len);
end

function m = func1(arr, len)
    %# @Drodbar: for-loop
    idx = 1;
    N = length(len);
    m = zeros(1,N);
    for i=1:N
        m(i) = mean( arr(idx+(0:len(i)-1)) );
        idx = idx + len(i);
    end
end

function m = func2(arr, len)
    %# @user1073959: MAT2CELL+CELLFUN
    m = cellfun(@mean, mat2cell(arr, 1, len));
end

function m = func3(arr, len)
    %# @Drodbar: ARRAYFUN+CELLFUN
    idx = arrayfun(@(a,b) a-(0:b-1), cumsum(len), len, 'UniformOutput',false);
    m = cellfun(@(a) mean(arr(a)), idx);
end

function m = func4(arr, len)
    %# @Amro: ACCUMARRAY
    endLocs = cumsum(len(:));
    subs = zeros(endLocs(end),1);
    subs([1;endLocs(1:end-1)+1]) = 1;
    subs = cumsum(subs);

    m = accumarray(subs, arr) ./ len(:);
    if isrow(len)
        m = m';
    end
end

以下はタイミングです。テストは、MATLABR2012aを搭載したWinXP32ビットマシンで実行されました。私の方法は、他のすべての方法よりも桁違いに高速です。Forループは2番目に優れています。

>> [t,v] = testMeans();
>> t
t =
   0.013098   0.013074   0.022407   0.00031807
    |           |          |          \_________ @Amro: ACCUMARRAY (!)
    |           |           \___________________ @Drodbar: ARRAYFUN+CELLFUN
    |            \______________________________ @user1073959: MAT2CELL+CELLFUN
     \__________________________________________ @Drodbar: FOR-loop

さらに、すべての結果は正しく、等しいです。違いはeps、マシンの精度のオーダーであり(丸め誤差を累積するさまざまな方法によって引き起こされます)、したがって、ごみと見なされ、単に無視されます。

%#assert( isequal(v{:}) )
>> maxErr = max(max( diff(vertcat(v{:})) ))
maxErr =
   3.3307e-16
于 2012-07-29T15:39:13.537 に答える
0

arrayfunこれがとを使用した解決策ですcellfun

zarray  = [1 2 3 4 5 6 7 8 9 10];
lengths = [1 3 2 1 3];

% Generate the indexes for the elements contained within each length specified
% subset. idx would be {[1], [4, 3, 2], [6, 5], [7], [10, 9, 8]} in this case
idx = arrayfun(@(a,b) a-(0:b-1), cumsum(lengths), lengths,'UniformOutput',false);
means = cellfun( @(a) mean(zarray(a)), idx);

希望する出力結果:

means =

    1.0000    3.0000    5.5000    7.0000    9.0000

@tmpearceのコメントに続いて、上記のソリューション間でパフォーマンスをすばやく比較し、そこから次の関数を作成しました。subsetMeans1

function means = subsetMeans1( zarray, lengths)

% Generate the indexes for the elements contained within each length specified
% subset. idx would be {[1], [4, 3, 2], [6, 5], [7], [10, 9, 8]} in this case
idx = arrayfun(@(a,b) a-(0:b-1), cumsum(lengths), lengths,'UniformOutput',false);
means = cellfun( @(a) mean(zarray(a)), idx);

単純なforループの代替関数である関数subsetMeans2

function means = subsetMeans2( zarray, lengths)

% Method based on single loop
idx = 1;
N = length(lengths);
means = zeros( 1, N);
for i = 1:N
    means(i) = mean( zarray(idx+(0:lengths(i)-1)) );
    idx = idx+lengths(i);
end

TIMEITに基づく次のテストスクリプトを使用して、入力ベクトル上の要素の数とサブセットごとの要素のサイズを変化させてパフォーマンスをチェックできるようにします。

% Generate some data for the performance test

% Total of elements on the vector to test
nVec = 100000;

% Max of elements per subset
nSubset = 5;

% Data generation aux variables
lenghtsGen = randi( nSubset, 1, nVec);
accumLen = cumsum(lenghtsGen);
maxIdx = find( accumLen < nVec, 1, 'last' );

% % Original test data
% zarray  = [1 2 3 4 5 6 7 8 9 10];
% lengths = [1 3 2 1 3];

% Vector to test
zarray = 1:nVec;
lengths = [ lenghtsGen(1:maxIdx) nVec-accumLen(maxIdx)] ;

% Double check that nVec is will be the max index
assert ( sum(lengths) == nVec)

t1(1) = timeit(@() subsetMeans1( zarray, lengths));
t1(2) = timeit(@() subsetMeans2( zarray, lengths));

fprintf('Time spent subsetMeans1: %f\n',t1(1));
fprintf('Time spent subsetMeans2: %f\n',t1(2));

おそらくこれらの関数の余分なオーバーヘッドが原因で、ベクトル化されていないバージョンの方が高速arrayfunであることがわかります。cellfun

Time spent subsetMeans1: 2.082457
Time spent subsetMeans2: 1.278473
于 2012-07-28T14:40:41.703 に答える