Vectorized range checking in Matlab - arrays

In trying to port an algorithm from C# to Matlab I found that Matlab is inefficient at running for loops. As such I want to vectorize the algorithm.
I have following inputs:
lowrange:
[ 00 10 20 30 40 50 ... ]
highrange:
[ 10 20 30 40 50 60 ... ]
These arrays are equal in length.
I now have a third array Values (which could be any length) and for this array I want to count the occurrences of Values elements between lowerange(i) and highrange(i) (You can see I'm coming from a for loop).
The output should be an array of length lowrange/highrange.
So with the above arrays and input LineData:
[ 1 2 3 4 6 11 12 16 31 34 45 ]
I expect to get:
[ 05 03 00 02 01 00 ... ]
I tried the (for me) obvious thing:
LineData(LineData < PixelEnd & LineData > PixelStart)
But that doesn't work because it just checks LineData on an element by element way. It does not try to apply the comparison over all values in LineData.
Unfortunately, I cannot come up with anything else since I'm not yet used to think in a Matlab 'vector' way, let alone knowing all applicable instructions from memory.

As you are looking to do a basic histogram with given edges, you can use Matlabs built-in function histc:
values = [ 1 2 3 4 6 11 12 16 31 34 45 ];
edges = 0:10:60;
histc(values, edges)
ans =
5 3 0 2 1 0 0

For ranges with identical intervals and starting from 0, here's a bsxfun based counting approach -
LineData = [ 1 2 3 4 6 11 12 16 31 34 45 ] %// Input
interval = 10; %// interval width
num_itervals = 6; %// number of intervals
%// Get matches for each interval and sum them within each interval for the counts
out = sum(bsxfun(#eq,ceil(LineData(:)/interval),1:num_itervals))
Output -
LineData =
1 2 3 4 6 11 12 16 31 34 45
out =
5 3 0 2 1 0
Assuming that the last interval would be the one holding the max of input data, you can try out a diff + indexing based approach too -
LineData = [ 1 2 3 4 6 11 12 16 31 34 45 ] %// Input
interval = 10; %// interval width
labels = ceil(LineData(:)/interval); %// set labels to each input entry
df_labels = diff(labels)~=0; %// mark the change of labels
df_labels_pos = find([df_labels; 1]); %// get the positions of label change
intv_pos= labels([true;df_labels]);%// position of each interval with nonzero counts
%// get counts from interval between label position change and put at right places
out(intv_pos) = [ df_labels_pos(1) ; diff(df_labels_pos)];

Related

Matlab: extract values from vector A, based on values in vector B

A = [5 10 16 22 28 32 36 44 49 56]
B = [2 1 1 2 1 2 1 2 2 2]
How to get this?
C1 = [10 16 28 36]
C2 = [5 22 32 44 49 56]
C1 needs to get the values from A, only in the positions in which B is 1
C2 needs to get the values from A, only in the positions in which B is 2
You can do this this way :
C1 = A(B==1);
C2 = A(B==2);
B==1 gives a logical array : [ 0 1 1 0 1 0 1 0 0 0 ].
A(logicalArray) returns elements for which the value of logicalArray is true (it is termed logical indexing).
A and logicalArray must of course have the same size.
It is probably the fastest way of doing this operation in matlab.
For more information on indexing, see matlab documentation.
To achieve this with an arbitrary number of groups (not just two as in your example), use accumarray with an a anoynmous function to collect the values in each group into a cell. To preserve order, B needs to be sorted first (and the same order needs to be applied to A):
[B_sort, ind_sort] = sort(B);
C = accumarray(B_sort.', A(ind_sort).', [], #(x){x.'});
This gives the result in a cell array:
>> C{1}
ans =
10 16 28 36
>> C{2}
ans =
5 22 32 44 49 56

Find median position points of duration events

I have the following vector A:
A = [34 35 36 5 6 7 78 79 7 9 10 80 81 82 84 85 86 102 3 4 6 103 104 105 106 8 11 107 201 12 202 203 204];
For n = 2, I counted the elements larger or equal to 15 within A:
D = cellfun(#numel, regexp(char((A>=15)+'0'), [repmat('0',1,n) '+'], 'split'));
The above expression gives the following output as duration values:
D = [3 2 7 4 6] = [A(1:3) **stop** A(7:8) **stop** A(12:18) **stop** A(22:25) **stop** A(28:33)];
The above algorithm computes the duration values by counting the elements larger or equal to 15. The counting also allows less than 2 consecutive elements smaller than 15 (n = 2). The counter stops when there are 2 or more consecutive elements smaller than 15 and starts over at the next substring within A.
Eventually, I want a way to find the median position points of the duration events A(1:3), A(7:8), A(12:18), A(22:25) and A(28:33), which are correctly computed. The result should look like this:
a1 = round(median(A(1:3))) = 2;
a2 = round(median(A(7:8))) = 8;
a3 = round(median(A(12:18))) = 15;
a4 = round(median(A(22:25))) = 24;
a5 = round(median(A(28:33))) = 31;
I edited the question to make it more clear, because the solution that was provided here assigns the last number within the row of 2 or more consecutive numbers smaller than 15 (3 in this case) after A(1:3) to the next substring A(7:8)and the same with the other substrings, therefore generating wrong duration values and in consequence wrong median position points of the duration events when n = 2 or for any given even n.
Anyone has any idea how to achieve this?

Matlab: reshape 3-dimensional array into 2-dimensional array

I have a 3x3x2000 array of rotation matrices that I need to transform into a 2000x9 array.
I think I have to use a combination of permute() and reshape(), but I don't get the correct output order.
This is what I need:
First row of 3x3 array needs to be columns 1:3 in the output
Second row of 3x3 array needs to be columns 4:6 in the output
Third row of 3x3 array needs to be columns 7:9 in the output
I have tried all possible combinations of numbers 1 2 3 in the following code:
out1 = permute(input, [2 3 1]);
out2 = reshape(out1, [2000 9]);
But I always end up with the wrong order. Any tips for a Matlab newbie?
How about a simple for-loop?
for i=1:size(myinput,3)
myoutput(i,:)=[myinput(1,:,i) myinput(2,:,i) myinput(3,:,i)];
% or
% myoutput(i,:)=reshape(myinput(:,:,i),[],9);
end
It's not simple as using permute and reshape, but it is transparent and easier for debugging. Once everything in your program runs perfectly, you can consider to rewrite such for-loops in your code...
You had a mix-up in your permute
a = reshape(1:9*6, 3, 3, []);
a is a 3x3x6 matrix, each
a(:,:,i) = 9*(i-1) + [1 4 7
2 5 8
3 6 9];
So
out1 = permute(a, [3,1,2]);
out2 = reshape(out1, [], 9);
Or in one line
out3 = reshape(permute(a, [3,1,2]), [], 9);
So
out2 = out3 =
1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18
19 20 21 22 23 24 25 26 27
28 29 30 31 32 33 34 35 36
37 38 39 40 41 42 43 44 45
46 47 48 49 50 51 52 53 54

check if ALL elements of a vector are in another vector

I need to loop through coloumn 1 of a matrix and return (i) when I have come across ALL of the elements of another vector which i can predefine.
check_vector = [1:43] %% I dont actually need to predefine this - i know I am looking for the numbers 1 to 43.
matrix_a coloumn 1 (which is the only coloumn i am interested in looks like this for example
1
4
3
5
6
7
8
9
10
11
12
13
14
16
15
18
17
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
1
3
4
2
6
7
8
We want to loop through matrix_a and return the value of (i) when we have hit all of the numbers in the range 1 to 43.
In the above example we are looking for all the numbers from 1 to 43 and the iteration will end round about position 47 in matrix_a because it is at this point that we hit number '2' which is the last number to complete all numbers in the sequence 1 to 43.
It doesnt matter if we hit several of one number on the way, we count all those - we just want to know when we have reached all the numbers from the check vector or in this example in the sequence 1 to 43.
Ive tried something like:
completed = []
for i = 1:43
complete(i) = find(matrix_a(:,1) == i,1,'first')
end
but not working.
Assuming A as the input column vector, two approaches could be suggested here.
Approach #1
With arrayfun -
check_vector = [1:43]
idx = find(arrayfun(#(n) all(ismember(check_vector,A(1:n))),1:numel(A)),1)+1
gives -
idx =
47
Approach #2
With customary bsxfun -
check_vector = [1:43]
idx = find(all(cumsum(bsxfun(#eq,A(:),check_vector),1)~=0,2),1)+1
To find the first entry at which all unique values of matrix_a have already appeared (that is, if check_vector consists of all unique values of matrix_a): the unique function almost gives the answer:
[~, ind] = unique(matrix_a, 'first');
result = max(ind);
Someone might have a more compact answer but is this what your after?
maxIndex = 0;
for ii=1:length(a)
[f,index] = ismember(ii,a);
maxIndex=max(maxIndex,max(index));
end
maxIndex
Here is one solution without a loop and without any conditions on the vectors to be compared. Given two vectors a and b, this code will find the smallest index idx where a(1:idx) contains all elements of b. idx will be 0 when b is not contained in a.
a = [ 1 4 3 5 6 7 8 9 10 11 12 13 14 16 15 18 17 19 20 21 22 23 24 25 26 ...
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 1 3 4 2 6 7 8 50];
b = 1:43;
[~, Loca] = ismember(b,a);
idx = max(Loca) * all(Loca);
Some details:
ismember(b,a) checks if all elements of b can be found in a and the output Loca lists the indices of these elements within a. The index will be 0, if the element cannot be found in a.
idx = max(Loca) then is the highest index in this list of indices, so the smallest one where all elements of b are found within a(1:idx).
all(Loca) finally checks if all indices in Loca are nonzero, i.e. if all elements of b have been found in a.

Matlab - replicating arrays values according to occurrences array

For example, A = [19 20 21 22 23 24 25]; B = [2 0 3 0 0 0 2];
How can we get a new array, repeating each value from B accordingly X times?
For example, answer here is: [19 19 21 21 21 25 25].
Please note that I am only allowed to a for loop combined with a repmat call.
If you are only allowed to use repmat and a for loop, you can do the following:
S = [];
for idx = 1 : length(B)
S = [S repmat(A(idx), 1, B(idx))];
end
S is initially a blank array, then for as many values as there are in B (or A since they're both equal in length), simply concatenate S with each value in A that is repeated by the corresponding number in B. S will contain the output.
By running the above example, I get:
S =
19 19 21 21 21 25 25
However, I highly recommend you use more vectorized approaches. I'll leave that to you as an exercise.
Good luck!

Resources