Кумулятивная сумма по индексу в MATLAB
рассмотрим следующую матрицу, где первый столбец-индекс, второй - значения, третий-совокупная сумма, которая сбрасывается после изменения индекса:
1 1 1 % 1
1 2 3 % 1+2
1 3 6 % 3+3
2 4 4 % 4
2 5 9 % 4+5
3 6 6 % 6
3 7 13 % 6+7
3 8 21 % 13+8
3 9 30 % 21+9
4 10 10 % 10
4 11 21 % 10+11
как можно получить третий столбец, избегая циклов?
Я пробую следующее:
A = [1 1;... % Input
1 2;...
1 3;...
2 4;...
2 5;...
3 6;...
3 7;...
3 8;...
3 9;...
4 10;...
4 11];
CS = cumsum(A(:,2)); % cumulative sum over the second column
I = [diff(data(:,1));0]; % indicate the row before the index (the first column)
% changes
offset=CS.*I; % extract the last value of cumulative sum for a given
% index
offset(end)=[]; offset=[0; offset] %roll offset 1 step forward
[A, CS, offset]
результат:
ans =
1 1 1 0
1 2 3 0
1 3 6 0
2 4 10 6
2 5 15 0
3 6 21 15
3 7 28 0
3 8 36 0
3 9 45 0
4 10 55 45
4 11 66 0
таким образом, проблема была бы решена, если бы существовал тривиальный способ преобразования четвертого столбца матрицы выше в
O =
0
0
0
6
6
15
15
15
15
45
45
так как CS-O дает желаемый результат.
Я был бы признателен за любые предложения.
3 ответов
использовать accumarray
С пользовательской функцией:
result = accumarray(A(:,1), A(:,2), [], @(x) {cumsum(x)});
result = vertcat(result{:});
это работает независимо от изменения индекса на Шаг 1 (как в вашем примере) или нет.
следующий подход быстрее, поскольку он избегает ячеек. См. отличный бенчмаркинг @Divakar в ответ (и посмотрите его решение, которое является самым быстрым):
-
если изменения индекса всегда соответствуют увеличению на 1 (как в вашем пример):
last = find(diff(A(:,1)))+1; %// index of last occurrence of each index value result = A(:,2); %// this will be cumsum'd, after correcting for partial sums correction = accumarray(A(:,1), A(:,2)); %// correction to be applied for cumsum result(last) = result(last)-correction(1:end-1); %// apply correction result = cumsum(result); %// compute result
-
если значение индекса может измениться более чем на 1 (т. е. могут быть "пропущены" значения): это требует небольшой модификации, которая немного замедляет работу.
last = find(diff(A(:,1)))+1; %// index of last occurrence of each index value result = A(:,2); %// this will be cumsum'd, after correcting for partial sums correction = accumarray(A(:,1), A(:,2), [], @sum, NaN); %// correction correction = correction(~isnan(correction)); %// remove unused values result(last) = result(last)-correction(1:end-1); %// apply correction result = cumsum(result);
cumsum
и diff
метод, и как таковой может быть хорошо с производительностью -
%// cumsum values for the entire column-2
cumsum_vals = cumsum(A(:,2));
%// diff for column-1
diffA1 = diff(A(:,1));
%// Cumsum after each index
cumsum_after_each_idx = cumsum_vals([diffA1 ;0]~=0);
%// Get cumsum for each "group" and place each of its elements at the right place
%// to be subtracted from cumsum_vals for getting the final output
diffA1(diffA1~=0) = [cumsum_after_each_idx(1) ; diff(cumsum_after_each_idx)];
out = cumsum_vals-[0;cumsum(diffA1)];
бенчмаркинг
если вы заботитесь о производительности, вот некоторые критерии против других решений, основанных на accumarray
.
бенчмаркинг кода (с комментариями удалены для компактности) -
A = .. Same as in the question
num_runs = 100000; %// number of runs
disp('---------------------- With cumsum and diff')
tic
for k1=1:num_runs
cumsum_vals = cumsum(A(:,2));
diffA1 = diff(A(:,1));
cumsum_after_each_idx = cumsum_vals([diffA1 ;0]~=0);
diffA1(diffA1~=0) = [cumsum_after_each_idx(1) ; diff(cumsum_after_each_idx)];
out = cumsum_vals-[0;cumsum(diffA1)];
end
toc,clear cumsum_vals diffA1 cumsum_after_each_idx out
disp('---------------------- With accumarray - version 1')
tic
for k1=1:num_runs
result = accumarray(A(:,1), A(:,2), [], @(x) {cumsum(x)});
result = vertcat(result{:});
end
toc, clear result
disp('--- With accumarray - version 2 (assuming consecutive indices only)')
tic
for k1=1:num_runs
last = find(diff(A(:,1)))+1; %// index of last occurrence of each index value
result = A(:,2); %// this will be cumsum'd, after correcting for partial sums
correction = accumarray(A(:,1), A(:,2)); %// correction to be applied for cumsum
result(last) = result(last)-correction(1:end-1); %// apply correction
result = cumsum(result); %// compute result
end
toc, clear last result correction
disp('--- With accumarray - version 2 ( general case)')
tic
for k1=1:num_runs
last = find(diff(A(:,1)))+1; %// index of last occurrence of each index value
result = A(:,2); %// this will be cumsum'd, after correcting for partial sums
correction = accumarray(A(:,1), A(:,2), [], @sum, NaN); %// correction
correction = correction(~isnan(correction)); %// remove unused values
result(last) = result(last)-correction(1:end-1); %// apply correction
result = cumsum(result);
end
toc
результат -
---------------------- With cumsum and diff
Elapsed time is 1.688460 seconds.
---------------------- With accumarray - version 1
Elapsed time is 28.630823 seconds.
--- With accumarray - version 2 (assuming consecutive indices only)
Elapsed time is 2.416905 seconds.
--- With accumarray - version 2 ( general case)
Elapsed time is 4.839310 seconds.
ваша стратегия на самом деле то, что я, возможно, сделал. Ваш последний шаг может быть достигнут таким образом: (помните, однако, что ваш подход предполагает последовательные индексы. Вы можете, конечно, изменить это через offset=[0; CS(1:end-1).*(diff(A(:,1))~=0)];
, но все равно нужны отсортированные индексы.)
I = find(offset);
idxLastI = cumsum(offset~=0);
hasLastI = idxLastI~=0; %// For the zeros at the beginning
%// Combine the above to the output
O = zeros(size(offset));
O(hasLastI) = offset(I(idxLastI(hasLastI)));
out = CS-O;
это должно быть сопоставимо с Divakar ' s cumsum
-diff
подход.