Average part of a multidimensional array based on another array (Matlab) - matlab

B = randn(1,25,10);
Z = [1;1;1;2;2;3;4;4;4;3];
Ok, so, I want to find the locations where Z=1(or any numbers that are equal to each other), then average across each of the 25 points at these specific locations. In the example you would end with a 1*25*4 array.
Is there an easy way to do this?
I'm not the most versed in Matlab.

First things first: break down the problem.
Define the groups (i.e. the set of unique Z values)
Find elements which belong to these groups
Take the average.
Once you have done that, you can begin to see it's a pretty standard for loop and "Select columns which meet criteria".
Something along the lines of:
B = randn(1,25,10);
Z = [1;1;1;2;2;3;4;4;4;3];
groups = unique(Z); %//find the set of groups
C = nan(1,25,length(groups)); %//predefine the output space for efficiency
for gi = 1:length(groups) %//for each group
idx = Z == groups(gi); %//find it's members
C(:,:,gi) = mean(B(:,:,idx), 3); %//select and mean across the third dimension
end

If B = randn(10,25); then it's very easy because Matlab function usually works down the rows.
Using logical indexing:
ind = Z == 1;
mean(B(ind,:));
If you're dealing with multiple dimensions use permute (and reshape if you actually have 3 dimensions or more) to get yourself to a point where you're averaging down the rows as above:
B = randn(1,25,10);
BB = permute(B, [3,2,1])
continue as above

Related

Perform an operation on indexed/grouped elements of an array in Matlab [duplicate]

I'm working in Matlab.
I have a two-dimensional matrix with two columns. Lets consider elements in the first column as labels. Labels may be repeated.
How to multiply all elements in the second column for every label?
Example:
matrix = [1,3,3,1,5; 2,3,7,8,3]'
I need to get:
a = [1,3,5; 16,21,3]'
Can you help me with doing it without for-while cycles?
I would use accumarray. The preprocessing with unique assigns integer indices 1:n to the values in the first row, which allow accumarray to work without creating unnecessary bins for 2 and 4. It also enables the support for negative numbers and floats.
[ulable,~,uindex]=unique(matrix(:,1))
r=accumarray(uindex,matrix(:,2),[],#prod)
r=[ulable,r]
/You can also use splitapply:
[ulable,~,uindex]=unique(matrix(:,1))
r=splitapply(#prod,matrix(:,2),uindex)
r=[ulable,r]
You can do it without loops using accumarray and the prod function:
clear
clc
matrix = [1,3,3,1,5; 2,3,7,8,3]';
A = unique(matrix,'rows');
group = A(:,1);
data = A(:,2);
indices = [group ones(size(group))];
prods = accumarray(indices, data,[],#prod); %// As mentionned by #Daniel. My previous answer had a function handle but there is no need for that here since prod is already defined in Matlab.
a = nonzeros(prods)
Out = [unique(group) a]
Out =
1 16
3 21
5 3
Check Lauren blog's post here, accumarray is quite interesting and powerful!
Try something like this, I'm sure it can be improved...
unValues = unique(matrix(:,1));
bb = ones(size(unValues));
for ii = 1:length(unValues)
bb(ii) = bb(ii)*prod(matrix(matrix(:, 1) == unValues(ii), 2));
end
a = [unValues bb];

multiple curves intersecting one master line

Assuming I have a set of lines/curves and want to find at once where each one intersects with a selected one. Is it possible to vectorize this operation in matlab assuming I now all the equations that define the lines/curves?
lines to intersect with:
y_i=m_i*x+b_i; % i integer
master line
y=M*x+B;
Like in the figure I show.
I know I can do this one to one by:
M*x_inter+B=m_i*x_inter+b_i;
y_inter=M*x_inter+B;
and then put this in a for loop, But since the actual use of this is against hundreds of lines it would be more efficient to vectorize the operation.
You can just join all your m_i and b_i in column vectors and then solve it at once. Lets say you have 3 lines:
nlines = 3;
Bcol = ones(nlines ,1)*B; %your B value put into a column
Mcol = ones(nlines ,1)*M;
x_inter = (b - Bcol)./( Mcol - m); %this b contains all your b_i values, the same for m
y_inter = Mcol .*x_inter + Bcol
Obviously x_inter and y_inter wil be arrays and not single values

How to vectorize this Matlab loop

I need some help to vectorize the following operation since I'm a little confused.
So, I have a m-by-2 matrix A and n-by-1 vector b. I want to create a n-by-1 vector c whose entries should be the values of the second column of A whose line is given by the line where the correspondent value of b would fall...
Not sure if I was clear enough. Anyway, the code below does compute c correctly so you can understand what is my desired output. However, I want to vectorize this function since my real n and m are in the order of many thousands.
Note that values of bare non-integer and not necessarily equal to any of those in the first column of A (these ones could be non-integers too!).
m = 5; n = 10;
A = [(0:m-1)*1.1;rand(1,m)]'
b = (m-1)*rand(n,1)
[bincounts, ind] = histc(b,A(:,1))
for i = 1:n
c(i) = A(ind(i),2);
end
All you need is:
c = A(ind,2);

calculate maximum distance with large vectors

ok so I have 2 vectors (A and B) with different lengths (in this example lets say 100000 and 300000) and I wish to obtain the indexes that have the largest difference.
Something like this
distAB=bsxfun(#(v1,v2) abs(v1-v2),A,B));
[~,lin_indx]=max(distAB(:));
[x_indx,y_indx]=ind2sub(size(A),lin_indx)
The problem here is that my vectors A and B are too large and producing the matrix "distAB" is too expensive. I would wish to obtain the min directly with the bsxfun.
If you want to maximize distance, the search can be reduced to just two candidate pairs: max(A), min(B)) or min(A), max(B)).
So just try those two pairs:
[ma_val, ma_ind] = min(A);
[Ma_val, Ma_ind] = max(A);
[mb_val, mb_ind] = min(B);
[Mb_val, Mb_ind] = max(B);
diff1 = abs(Mb_val-ma_val);
diff2 = abs(Ma_val-mb_val);
if diff1 > diff2
result_ind_A = ma_ind;
result_ind_B = Mb_ind;
result_value = diff1;
else
result_ind_A = Ma_ind;
result_ind_B = mb_ind;
result_value = diff2;
end
If you want to minimize distance: sort the concatenation of A and B, keeping track of which element is from A and which from B:
C = sortrows([A(:) zeros(numel(A),1); B(:) ones(numel(B),1)] ,1);
%// C(k,2)==0 indicates element k comes from A; 1 indicates from B
Now, use a for loop to traverse all elements in C(:,1) that come from B. For each such element, find the two elements from A that are located closest above and to below left in C. Those are the only candidates from A to be nearest to that element from A.
So for each element from B you have two candidates from A, which reduces the complexity of the problem significantly.
You can calculate distAB this way with the built-in #minus which could be more efficient -
distAB = abs(bsxfun(#minus,A(:).',B(:)))
Luis' approach of sorting will probably be the fastest. But if you have the statistics toolbox installed, you could use the function knnsearch, which makes for a simple yet efficient solution.
(There are also some similar free versions of this on the File Exchange. Look for: kd tree nearest neighbor)
One extra benefit of this solution is that it also works for 2D, 3D, ..., nD data.
[Is, D] = knnsearch(A,B,'K',1);
[~,j] = min(D); i = Is(j);
[A(i), B(j)]

Matlab: Find first item in a vector that satisfies criteria, then skip over 100+ values to find the next first

In Matlab R2010a:
I am familiar with finding values based on criteria as well as finding the first value in a vector that satisfies criteria. However, how does one find X's and not Y's in the following example? In this case, X's are the first values of a group of values that are findable given my criteria, and there are multiple groups like this amidst thousands of junk values.
I have an vector with 10,000 or more values. Let J be junk values, while X and Y are both values my find criteria will pick up. X's are interesting to me because they are the 'first' values of a series of values that satisfy my criteria before becoming J's. Assume that there are hundreds or thousands more J's in between the X's and Y's, but here is a small example
[J,J,J,J,J,J,J,J,J,J,J,X,Y,Y,Y,Y,J,J,J,J,J,J,J,J,J,X,Y,Y,Y,Y,J];
Assuming that you're not doing something to strange with those Xs and Ys, this is quite easy. You just need to find the beginning of each cluster:
% Create data using your example (Y can equal X, but we make it different)
J = 1; X = 2; Y = 3;
A = [J,J,J,J,J,J,J,J,J,J,J,X,Y,Y,Y,Y,J,J,J,J,J,J,J,J,J,X,Y,Y,Y,Y,J];
a0 = (A==X); % Logical indices of A that match X condition
start = find([a0(1) diff(a0)]==1) % Start index of each group beginning with X
vals = A(start) % Should all be equal to X
which returns
start =
12 26
vals =
2 2
The J values don't even need to be all the same, just not equal to what ever you're detecting as X. You might also find my answer to this similar question helpful.
A = [J,J,J,J,J,J,J,J,J,J,J,X,Y,Y,Y,Y,J,J,J,J,J,J,J,J,J,X,Y,Y,Y,Y,J]; % created vector
I = A(A~=J); % separated out all values that are not junk
V = I(I==I(1)); % separated all values that match the first non-junk value