MatLab accumarray unexpectedly changing ordering - matlab

As long as I understood accumarray, it means "Making the nth row of the output: 1) find n in sub. 2) if n is in m1, m2, m3 th element in sub, 3) apply the function to m1,m2,m3 th element of val 4) that's the nth row of the output"
Am I wrong somewhere?
I ran the following code.
A = [2 10 13 ; 1 11 14; 1 12 10]
[U,ix,iu]= unique(A(:,1))
vals = reshape(A(:, 2:end).', [], 1)
subs = reshape(iu(:, ones(size(A, 2)-1,1)).', [], 1)
r2 = accumarray(subs, vals', [], #(x){x'})
r2{1}
r2{2}
A =
2 10 13
1 11 14
1 12 10
U =
1
2
ix =
3
1
iu =
2
1
1
vals =
10
13
11
14
12
10
subs =
2
2
1
1
1
1
r2 =
[1x4 double]
[1x2 double]
ans =
12 11 14 10
ans =
13 10
=========================
But I expected r{1} = 11 14 12 10, and r{2} = 10 13.
Why did accumarray suddenly changed the ordering?
How can I get the expected result?

The documentation of accumarray says:
Note If the subscripts in subs are not sorted, fun should not depend
on the order of the values in its input data.
And your subs is not sorted (at least not in ascending order). If you rewrite the code so that subs is sorted and vals is also rearranged accordingly you get the desired result:
A = [2 10 13 ; 1 11 14; 1 12 10]
[U,ix,iu]= unique(A(:,1))
vals = reshape(A(:, 2:end).', [], 1)
subs = reshape(iu(:, ones(size(A, 2)-1,1)).', [], 1)
[subs_sorted, I] = sort(subs);
r2 = accumarray(subs_sorted, vals(I)', [], #(x){x'})
r2{1}
r2{2}
And running this code returns:
ans =
11 14 12 10
ans =
10 13

Related

Combine index-based and logical addressing in Matlab

Consider a matrix X. I have to update a submatrix of X, X(row1:row2, col1:col2), with a matrix Z (of size row2-row1+1, col2-col1+1) but only on those positions where a logical matrix L (of size row2-row1+1, col2-col1+1) is true.
E.g. if
X=[ 1 2 3 4 5 6
11 12 13 14 15 16
21 22 23 24 25 26
31 32 33 34 34 36]
Z=[31 41
32 42]
L=[ 1 0
0 1]
row1 = 2; row2 = 3; col1 = 3; col2 = 4
then after the update I should get:
X=[ 1 2 3 4 5 6
11 12 31 14 15 16
21 22 23 42 25 26
31 32 33 34 34 36]
Currently I do the following:
Y = X(row1:row2, col1:col2);
Y(L) = Z(L);
X(row1:row2, col1:col2) = Y;
This code is in a tight loop and according to Matlab's (v2019a) profiler is the main bottleneck of my program. In the real code X is a 2000x1500x3 cube; row1, row2, col1, col2, Z and L change in the loop.
The question is whether it can be rewritten into a single / faster assignment.
Thanks.
Honestly, without seeing your actual code, I get the sense that your solution may be as fast as you can get. The reason I say that is because I tested a few different solutions by creating some random sample data closer to your actual problem. I assumed X is an image of type uint8 with size 2000-by-1500-by-3, Z is size N-by-N (i.e. we will only be modifying the first page of X), L is an N-by-N logical array, and the row and column indices are randomly chosen:
X = randi([0 255], 2000, 1500, 3, 'uint8');
N = 20; % Submatrix size
Z = randi([0 255], N, N, 'uint8');
L = (rand(N, N) > 0.5);
row1 = randi([1 2000-N]);
row2 = row1+N-1
col1 = randi([1 1500-N]);
col2 = col1+N-1;
I then tested 3 different solutions: your original solution, a solution using find and sub2ind to create a linear index for X, and a solution that creates a logical index for X:
% Original solution:
Y = X(row1:row2, col1:col2, 1);
Y(L) = Z(L);
X(row1:row2, col1:col2, 1) = Y;
% Linear index solution:
[rIndex, cIndex] = find(L);
X(sub2ind(size(X), rIndex+row1-1, cIndex+col1-1)) = Z(L);
% Logical index solution
[R, C, ~] = size(X);
fullL = false(R, C);
fullL(row1:row2, col1:col2) = L;
X(fullL) = Z(L);
I tested these repeatedly with randomly-generated sample data using timeit and found that your original solution is consistently the fastest. The linear index solution is very close, but slightly slower. The logical index solution takes more than twice as long.
Let's define some example data:
X = randi(9,5,6);
Y = 10+X;
row1 = 2;
row2 = 4;
col1 = 3;
col2 = 4;
L = logical([0 1; 0 0; 1 1]);
Then:
ind_subm = bsxfun(#plus, (row1:row2).',size(X,1)*((col1:col2)-1));
% linear index for submatrix
ind_subm_masked = ind_subm(L);
% linear index for masked submatrix
X(ind_subm_masked) = Y(ind_subm_masked);
Example results:
X before:
X =
6 2 1 7 9 6
3 3 3 5 5 7
6 3 8 6 5 4
7 4 1 3 3 4
2 5 9 5 5 9
L:
L =
3×2 logical array
0 1
0 0
1 1
X after:
X =
6 2 1 7 9 6
3 3 3 15 5 7
6 3 8 6 5 4
7 4 11 13 3 4
2 5 9 5 5 9

Create new matrix based on diagonal and antidiagonal of given matrix

I want to create matrix B from matrix A, with the following rules:
Non-diagonal elements A are like non-diagonal elements of B.
The main diagonal of A is the antidiagonal of B
The antidiagonal of A is the main diagonal of B.
For example:
A = [ 1 2 3 4;
7 8 9 10;
13 14 15 16;
19 20 21 22 ];
B = [ 4 2 3 1;
7 9 8 10;
13 15 14 16;
22 20 21 19 ];
How can I create B given A?
You can create all of the indices, then it's a single assignment.
% Get size of square matrix A
n = size(A,1);
% Indicies are 1:n^2 by default
idx = 1:n^2;
% Swap diagonal and antidiagonal indices
idx( [1:(n+1):n^2, n^2-n+1:1-n:n] ) = [n^2-n+1:1-n:n, 1:(n+1):n^2];
% Use the indexing array to create B from A, reshape to be n*n
B = reshape( A( idx ), n, n );
Output for your example A:
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
There are so many ways to reach that results, it is just an indexing exercise. Here is one (of the many) way to reach that result for any square matrix of size n:
%% input
A=[ 1 2 3 4 ;
7 8 9 10 ;
13 14 15 16 ;
19 20 21 22 ];
%% Calculate linear indices for the diagonal and antidiagonal
n=size(A,1) ;
idxdiag = 1:(n+1):n^2 ; % => idxdiag = [1 6 11 16]
idxantidiag = n:(n-1):n^2-1 ; % => idxantidiag = [4 7 10 13]
%% Generate B
B = A ; % start with a simple copy (for the non-diagonal elements)
% Method 1: direct indice assignment
B(idxdiag) = diag(fliplr(A)) ; % Assign diagonal elements of B
B(idxantidiag) = flipud(diag(A)) ; % Assign antidiagonal elements of B
% Method 2: summation
B([idxdiag idxantidiag]) = 0 ;
B = B + diag(diag(fliplr(A))) + fliplr(diag(diag(A))) ;
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
Both methods return exactly the same matrix B.
I suggest you familiarise yourself with the MATLAB function used to understand what is going on behind the scene:
fliplr
flipud
diag
and may be have a read at:
Matrix Indexing in MATLAB
I thought a little differently and came to a conclusion
A=[1 2 3 4;7 8 9 10;13 14 15 16; 19 20 21 22];;
n=size(A,1) ;
B=zeros(n,n) ;
for i=1:n
for j=1:n
if i==j
B(i,j)=A(i,n-i+1);
elseif j==n-i+1
B(i,j)=A(i,i);
else
B(i,j)=A(i,j);
end
end
end
B
Here's a variant using eye, find, and flip to generate linear indices:
ind1 = find(eye(size(A)));
ind2 = flip(find(flip(eye(size(A)))));
B = A;
B([ind1 ind2]) = B([ind2 ind1]);
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
And here's a variant of the above that uses just eye and flip to generate logical indices:
ind1 = eye(size(A), 'logical');
ind2 = flip(ind1);
B = A;
B(ind1) = flip(A(ind2));
B(ind2) = flip(A(ind1));
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19

Matlab determine index position in matrix

Hin everyone..I have matrix
col = [1 2 3 9 10 15 16 17]
I need to divide A into 3 with length B = [3 2 3],
result required :
col1 = [1 2 3] -> from col(1:3)
col2 = [9 10] -> from col(4:5)
col3 = [15 16 17] -> from col(6:8)
Thank you so much...
mat2cell can be use:
A = [1 2 3 9 10 15 16 17];
B = [3 2 3];
mat2cell(A,1, B)
Result:
{
[1,1] =
1 2 3
[1,2] =
9 10
[1,3] =
15 16 17
}
I assume that lenghts in B match col length, so every element is accounted for. If so, you can do it using simple for loop as follows:
col = [1 2 3 9 10 15 16 17];
B = [3 2 3];
start_idx = 1;
for b = B
col_part = col(start_idx : start_idx+b-1)
start_idx = start_idx+b;
end
Results in:
col_part =
1 2 3
col_part =
9 10
col_part =
15 16 17

Find top n elements in matrix

I have a matrix which contains values and I wish to find the index of the top n minimum values.
I use the following code for finding the minimum most value:
[r,c]=find(Result==min(min(Result)));
I cant find any other questions on stack overflow which answer the question, please help
Maybe you could do something like this:
sorted = sort(Result(:));
topten = sorted(1:10);
[~,ia,~] = intersect(Result(:),topten(:)); % // Get the indices of the top ten values
[r,c]=ind2sub(size(Result),ia); % // Convert the indices to rows and columns
Or without Intersect in the other answer
[sorted,I] = sort(Result(:));
[r,c] = ind2sub(size(Result),I(1:10)); %//Change 10 to any other required value
Use prctile (Statistics Toolbox) to find the appropriate threshold, and then use indexing to select the elements above that threshold:
x = magic(4); %// example
n = 5; %// we want the top n elements
M = numel(x);
p = prctile(x(:), (M-n)/M*100);
indices = find(x>p); %// result in the form linear indices
[row, col] = find(x>p); %// result in the form of row and column indices
In this example:
>> x
x =
16 2 3 13
5 11 10 8
9 7 6 12
4 14 15 1
>> indices.'
ans =
1 8 12 13 15
>> row.'
ans =
1 4 4 1 3
>> col.'
ans =
1 2 3 4 4
>> x(indices).'
ans =
16 14 15 13 12
Example with repeated elements:
>> x = [1 1 2 5; 3 4 3 5];
>> n = 5;
gives
>> indices.'
ans =
2 4 6 7 8
>> row.'
ans =
2 2 2 1 2
>> col.'
ans =
1 2 3 4 4
>> x(indices).'
ans =
3 4 3 5 5

Average values in one file based upon values in another

I have a problem which I hope contributors can help me solve. I think it is best just to provide a working example:
I have two cells both of which consist of the same number of matrices (the result of reading a series of data files followed by some loop calculations). Each matrix is a column of decimal year days followed by a series of columns of data. Here is the dummy data:
A = [ 186.356 1 2 3 4;186.364 2 3 4 5;186.372 3 4 5 6]
B = [ 187.356 1 2 3 4;187.364 2 3 4 5;187.372 3 4 5 6]
C = [ 188.356 1 2 3 4;188.364 2 3 4 5;188.372 3 4 5 6]
x = {A,B,C}
D = [ 186.3568 1 2 3 4; 186.3576 2 3 4 5; 186.3584 3 4 5 6; 186.3592 4 5 6 7; 186.36 5 6 7 8; 186.3608 6 7 8 9; 186.3616 7 8 9 10; 186.3624 8 9 10 11; 186.3632 9 10 11 12; 186.364 10 11 12 13; 186.3648 11 12 13 14; 186.3656 12 13 14 15]
E = [ 187.3568 1 2 3 4; 187.3576 2 3 4 5; 187.3584 3 4 5 6; 187.3592 4 5 6 7; 187.36 5 6 7 8; 187.3608 6 7 8 9; 187.3616 7 8 9 10; 187.3624 8 9 10 11; 187.3632 9 10 11 12; 187.364 10 11 12 13; 187.3648 11 12 13 14; 187.3656 12 13 14 15]
F = [ 188.3568 1 2 3 4; 188.3576 2 3 4 5; 188.3584 3 4 5 6; 188.3592 4 5 6 7; 188.36 5 6 7 8; 188.3608 6 7 8 9; 188.3616 7 8 9 10; 188.3624 8 9 10 11; 188.3632 9 10 11 12; 188.364 10 11 12 13; 188.3648 11 12 13 14; 188.3656 12 13 14 15]
y = {D,E,F}
My intention is to sum the data columns contained within both x and y. However you can see that the resolution of the data in y is much higher than x therefore I would first like to average the data in y based upon the timesteps of x.
As an example the first time period which matches between x and y correspond to row 1 in matrix A but only the first 10 rows in matrix D. The sum of the first row in A is 10:
sumA = sum(A(1,2:end),2)
and the average of the first 10 rows in D is
sumD = sum(mean(D(1:10,2:end)),2)
resulting in a total of 38.
This is a simple example; I have many rows of data in two large cells. I suspect I need to extract the data from the cells, loop through the data whilst rewriting to another cell of the same dimensions as the first two cells, x and y but am at a loss as to where to start. Any help would be great.
Edit
In looking to clarify my problem I realise I made a mistake in the original question. This is no doubt the cause of the confusion.
Everything above is correct however the sum of the first 10 rows of D:
sumD = sum(mean(D(1:10,2:end)),2)
sumD =
28
should actually be added to the sum of the second row in A:
sumA = sum(A(2,2:end),2)
sumA =
14
This is because all the values in rows 1-10 of column 1 in matrix D are larger than the the value in row 1 and column 1 of matrix A but smaller than or equal to row 2 and column 2 of matrix A. It might be easier if increase the dummy data in matrix D:
D = [ 186.3568 1 2 3 4; 186.3576 2 3 4 5; 186.3584 3 4 5 6; 186.3592 4 5 6 7; 186.36 5 6 7 8; 186.3608 6 7 8 9; 186.3616 7 8 9 10; 186.3624 8 9 10 11; 186.3632 9 10 11 12; 186.364 10 11 12 13; 186.3648 11 12 13 14; 186.3656 12 13 14 15; 186.3664 13 14 15 16; 186.3672 14 15 16 17; 186.368 15 16 17 18; 186.3688 16 17 18 19; 186.3696 17 18 19 20; 186.3704 18 19 20 21; 186.3712 19 20 21 22; 186.372 20 21 22 23]
Now the result would be a two value vector. The first value would be 28+14, the result of the sum of the second row in A (or sumA) and the sum of the mean of the first 10 rows of data in matrix D (or sumD). The second value would the sum of the third row in A, lets say sumA2:
sumA2 = sum(A(3,2:end),2)
sumA2 =
18
and sumD2:
sumD2 = sum(mean(D(11:end,2:end)),2)
sumD2 =
68
sumA2+sumD2
ans =
86
I would like this process to be automated so that I can go through each matrix in the cell. i.e. if I start with cells x and y with dims:
x =
[300x5 double] [300x5 double] [300x5 double]
y =
[2000x5 double] [2000x5 double] [2000x5 double]
I would like the result to be
z =
[300x1 double] [300x1 double] [300x1 double]
I am not sure if that makes things any clearer but lets see!
Well, if I managed to properly get all your tricky specifications, here is the code:
function z = foo(x, y)
z = x;
for i = 1:length(x)
z{i} = sum(z{i}(:, 2:end), 2);
dmin = 0;
for j = 1:size(x{i}, 1)
dmax = x{i}(j, 1);
t = y{i}(:, 1);
mask = t > dmin & t <= dmax;
if any(mask)
z{i}(j) = z{i}(j) + sum(median(y{i}(mask, 2:end)), 2);
end
dmin = dmax;
end
end
end
For the given x and y from the question, for the answer z I have z{1} == z{2} == z{3}, and
>> z{1}
ans =
10
42
70
If I substitute D from your "Edit" section, I get z{1}(3) == 86, as you claimed.
Nothing special about the code. dmin and dmax hold current dates range based on the value of the first column of a matrix from x (i.e. A, B, etc.). An if any(mask) statement is needed to avoid taking median from empty array, which leads to a vector of NaN's, which screws up the sum.