Splitting Groups in MATLAB - matlab

Say I have the following matrix:
A(:,1) = [-5 -5 5 5 -5 -5 -5 -5 5 5 5 5]';
A(:,2) = [8 7 3 6 5 9 8 7 9 4 3 2 ]';
I'd like to split it into 4 groups based on the signs in the first column (i.e. everything prior to a sign flip is one group):
1) -5 -5
2) 5 5
3) -5 -5 -5 -5
4) 5 5 5 5
and the corresponding grouping in the second column would then be:
1) 8 7
2) 3 6
3) 5 9 8 7
4) 9 4 3 2
My intuition is to use
diff(A(:,1)) ~= 0
as the first step, but I am unsure how to continue from there. Any help would be appreciated, thanks!

You can use accumarray to create this cell array for you. We first need to assign a unique value to each "group" of consecutive numbers which share a sign. We can then use accumarray to place all elements in a given group into an element of a cell array.
A = cat(1, [-5 -5 5 5 -5 -5 -5 -5 5 5 5 5], [8 7 3 6 5 9 8 7 9 4 3 2 ]).';
% Compute the sign of each element: -1 for negative, 1 for positive
% Repeat first element for diff
S = sign(A([1 1:end],1));
% -1 -1 -1 1 1 -1 -1 -1 -1 1 1 1 1
% Compute element-by-element differences
D = diff(S);
% 0 0 2 0 -2 0 0 0 2 0 0 0
% Convert to a logical matrix which will make any non-zero 1 and any zero stays 0
L = logical(D);
% 0 0 1 0 1 0 0 0 1 0 0 0
% Take the cumulative sum (and add 1) to give each group of elements a unique number
subs = cumsum(L) + 1;
% 1 1 2 2 3 3 3 3 4 4 4 4
% Use this as the first input to accumarray and perform a given action on all elements in
% A(:,2) which share these values. Our action will be to convert to a cell array
result = accumarray(subs, A(:,2), [], #(x){x});
% result{1} =
% 8 7
%
% result{2} =
% 3 6
%
% result{3} =
% 5 9 8 7
%
% result{4} =
% 9 4 3 2
If we really want to reduce it so a single line we could do this.
accumarray(1 + cumsum(logical(diff(sign(A([1 1:end],1))))), A(:,2), [], #(x){x})

Use:
groupsSizes= diff([0;find(conv(A(:,1),[1,-1],'same')~=0)]);
firstGroup = mat2cell(A(:,1),groupsSizes,1);
secondGroup = mat2cell(A(:,2),groupsSizes,1);

Here's another way:
result = mat2cell(A(:), diff([0; find([diff(sign(A(:))); true])]));
This uses mat2cell to split A into pieces and put each into a cell. The length of the pieces is computed using sign to detect the sign and then diff and find to obtain the run lengths.

Related

Removing columns from a Matrix MatLab

[1 2 3 4 5 6 7 8 9 ;
9 8 7 6 5 4 3 2 1 ;
1 2 0 0 1 0 0 0 1 ]
The last row has five columns with zeros. I would like to keep only one column per zero crossing.
like this
[1 2 3 5 8 9 ;
9 8 7 5 2 1 ;
1 2 0 1 0 1 ]
Is this possible with fast Matlab functions or do I have to write some slow complicated for loop ?
You can create a logical array many different ways to find the columns to remove. Something like this would work
% Find the zeros that are not the first zero
cols_to_remove = data(end,:) == 0 & ~diff([false, data(end,:) == 0]) == 1;
% Now remove them
data(:, cols_to_remove) = [];

Shift rows in matrix with respect to vector values in Octave/MATLAB

Can I shift rows in matrix A with respect to values in vector v?
For instance A and v specified as follows:
A =
1 0 0
1 0 0
1 0 0
v =
0 1 2
In this case I want to get this matrix from A:
A =
1 0 0
0 1 0
0 0 1
Every i-th row in A has been shifted by i-th value in v
Can I do this operation with native functions?
Or should I write it by myself?
I've tried circshift function, but I couldn't figure out how to shift rows separately.
The function circshift does not work as you want and even if you use a vector for the amount of shift, that is interpreted as the amount of shift for each dimension. While it is possible to loop over the rows of your matrix, that will not be very efficient.
More efficient is if you compute the indexing for each row which is actually quite simple:
## First, prepare all your input
octave> A = randi (9, 4, 6)
A =
8 3 2 7 4 5
4 4 7 3 9 1
1 6 3 9 2 3
7 4 1 9 5 5
octave> v = [0 2 0 1];
octave> sz = size (A);
## Compute how much shift per row, the column index (this will not work in Matlab)
octave> c_idx = mod ((0:(sz(2) -1)) .- v(:), sz(2)) +1
c_idx =
1 2 3 4 5 6
5 6 1 2 3 4
1 2 3 4 5 6
6 1 2 3 4 5
## Convert it to linear index
octave> idx = sub2ind (sz, repmat ((1:sz(1))(:), 1, sz(2)) , c_idx);
## All you need is to index
octave> A = A(idx)
A =
8 3 2 7 4 5
9 1 4 4 7 3
1 6 3 9 2 3
5 7 4 1 9 5
% A and v as above. These could be function input arguments
A = [1 0 0; 1 0 0; 1 0 0];
v = [0 1 2];
assert (all (size (v) == [1, size(A, 1)]), ...
'v needs to be a horizontal vector with as many elements as rows of A');
% Calculate shifted indices
[r, c] = size (A);
tmp = mod (repmat (0 : c-1, r, 1) - repmat (v.', 1, c), c) + 1;
Out = A(sub2ind ([r, c], repmat ([1 : r].', 1, c), tmp))
Out =
1 0 0
0 1 0
0 0 1
If performance is an issue, you can replace repmat with an equivalent bsxfun call which is more efficient (I use repmat here for simplicity to demonstrate the approach).
With focus on performance, here's one approach using bsxfun/broadcasting -
[m,n] = size(A);
idx0 = mod(bsxfun(#plus,n-v(:),1:n)-1,n);
out = A(bsxfun(#plus,(idx0*m),(1:m)'))
Sample run -
A =
1 7 5 7 7
4 8 5 7 6
4 2 6 3 2
v =
3 1 2
out =
5 7 7 1 7
6 4 8 5 7
3 2 4 2 6
Equivalent Octave version to use automatic broadcasting would look something like this -
[m,n] = size(A);
idx0 = mod( ((n-v(:)) + (1:n)) -1 ,n);
out = A((idx0*m)+(1:m)')
Shift vector with circshift in loop, iterating row index.

How to insert elements in a vector at regular intervals in Matlab

I have a vector of 13 entities in Matlab.
a=[3 4 6 8 1 5 8 9 3 7 3 6 2]
I want to append values [1 2 3 4 5] at regular intervals at position 1 5 9 13 & 17.
The final value of a looks like this.
a=[1 3 4 6 2 8 1 5 3 8 9 3 4 7 3 6 5 2].
The values with italics show the appended values.
How can I do it?
Since you are looking for regular intervals, you can take advantage of the reshape and cat function:
a = [3 4 6 8 1 5 8 9 3 7 3 6 2];
v = [1 2 3 4 5];
l = [1 5 9 13 17];
interval = l(2)-l(1)-1; %computes the interval between inserts
amax = ceil(size(a,2)/interval) * interval; %calculating maximum size for zero padding
a(amax) = 0; %zero padding to allow `reshape`
b = reshape (a,[interval,size(v,2)]); %reshape into matrix
result = reshape(vertcat (v,b), [1,(size(b,1)+1)*size(b,2)]); %insert the values into the right position and convert back into vector
%remove padded zeros
final = result(result ~= 0) %remove the zero padding.
>>final =
Columns 1 through 16
1 3 4 6 2 8 1 5 3 8 9 3 4 7 3 6
Columns 17 through 18
5 2
Here's an approach using boolean-indexing -
% Inputs
a = [3 4 6 8 1 5 8 9 3 7 3 6 2]
append_vals = [1 2 3 4 5]
append_interval = 4 % Starting at 1st index
% Find out indices of regular intervals where new elements are to be inserted.
% This should create that array [1,5,9,13,17]
N_total = numel(a) + numel(append_vals)
append_idx = find(rem(0:N_total-1,append_interval)==0)
% Get boolean array with 1s at inserting indices, 0s elsewhere
append_mask = ismember(1:N_total,append_idx)
% Setup output array and insert new and old elements
out = zeros(1,N_total)
out(~append_mask) = a
out(append_mask) = append_vals
Alternatively, we can also use linear-indexing and avoid creating append_mask, like so -
% Setup output array and insert new and old elements
out = zeros(1,N_total)
out(append_idx) = append_vals
out(setdiff(1:numel(out),append_idx)) = a
a=[3 4 6 8 1 5 8 9 3 7 3 6 2]; % // Your original values
pos = [1 5 9 13 17]; % // The position of the values you want to insert
b=[1 2 3 4 5]; % // The values you want to insert
% // Pre-allocate a vector with the total size to hold the resulting values
r = zeros(size(a,2)+size(pos,2),1);
r(pos) = b % // Insert the appended values into the resulting vector first
r3 = r.' <1 % // Find the indices of the original values. These will be zero in the variable r but 1 in r3
ans =
0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1
ind= find(r3==1) % // Find the indices of the original values
ind =
2 3 4 6 7 8 10 11 12 14 15 16 18
r(ind) = a; % // Insert those into the resulting vector.
r.'
ans =
1 3 4 6 2 8 1 5 3 8 9 3 4 7 3 6 5 2
You can use this function to append a bunch of values to an existing vector, given their positions in the new vector:
function r=append_interval(a,v,p)
% a - vector with initial values
% v - vector containing values to be inserted
% p - positions for values in v
lv=numel(v); % number of elements in v vector
la=numel(a); % number of elements in a vector
column_a=iscolumn(a); % check if a is a column- or row- wise vector
tot_elements=la+lv;
% size of r is tha max between the total number of elements in the two vectors and the higher positin in vector p (in this case missing positions in a are filled with zeros)
lr=max([max(p) tot_elements]);
% initialize r as nan vector
r=zeros(column_a*(lr-1)+1,~column_a*(lr-1)+1)/0;
% set elements in p position to the corresponding values in v
r(p)=v;
% copy values in a in the remaining positions and fill with zeros missing entries (if any)
tot_missing_values=lr-tot_elements;
if(tot_missing_values)
remaining_values=cat(2-iscolumn(a),a,zeros(column_a*(tot_missing_values-1)+1,~column_a*(tot_missing_values-1)+1));
else
remaining_values=a;
end
% insert values
r(isnan(r))=remaining_values;
You can use row-wise or column-wise vectors; the orientation of r will be the same of that of a.
Input:
a =
3 4 6 8 1 5 8 9 3 7 3 6 2
v =
1 2 3 4 5
p =
1 5 9 13 17
Output:
>> append_interval(a,v,p)
ans =
1 3 4 6 2 8 1 5 3 8 9 3 4 7 3 6 5 2
Every sequence of positive positions is allowed and the function will pad for you with zeros the final vector, in case you indicate a position exceding the sum of the original vector and added items.
For example, if:
v3 =
1 2 3 4 5 6 90
p3 =
1 5 9 13 17 30 33
you get:
append_interval(a,v3,p3)
ans =
Columns 1 through 19
1 3 4 6 2 8 1 5 3 8 9 3 4 7 3 6 5 2 0
Columns 20 through 33
0 0 0 0 0 0 0 0 0 0 6 0 0 90
Hope this will help.

Dividing a vector to form different matrices

I have a two long vector. Vector one contains values of 0,1,2,3,4's, 0 represent no action, 1 represent action 1 and 2 represent the second action and so on. Each action is 720 sample point which means that you could find 720 consecutive twos then 720 consecutive 4s for example. Vector two contains raw data corresponding to each action. I need to create a matrix for each action ( 1, 2, 3 and 4) which contains the corresponding data of the second vector. For example matrix 1 should has all the data (vector 2 data) which occurred at the same indices of action 1. Any Help??
Example on small amount of data:
Vector 1: 0 0 1 1 1 0 0 2 2 2 0 0 1 1 1 0 0 2 2 2
Vector 2: 6 7 5 6 4 6 5 9 8 7 9 7 0 5 6 4 1 5 8 0
Result:
Matrix 1:
5 6 4
0 5 6
Matrix 2:
9 8 7
5 8 0
Here is one approach. I used a cell array to store the output matrices, hard-coding names for such variables isn't a good plan.
V1=[0 0 1 1 1 0 0 2 2 2 0 0 1 1 1 0 0 2 2 2]
V2=[6 7 5 6 4 6 5 9 8 7 9 7 0 5 6 4 1 5 8 0]
%// Find length of sequences of 1's/2's
len=find(diff(V1(find(diff(V1)~=0,1)+1:end))~=0,1)
I=unique(V1(V1>0)); %// This just finds how many matrices to make, 1 and 2 in this case
C=bsxfun(#eq,V1,I.'); %// The i-th row of C contains 1's where there are i's in V1
%// Now pick out the elements of V2 based on C, and store them in cell arrays
Matrix=arrayfun(#(m) reshape(V2(C(m,:)),len,[]).',I,'uni',0);
%// Note, the reshape converts from a vector to a matrix
%// Display results
Matrix{1}
Matrix{2}
Since, there is a regular pattern in the lengths of groups within Vector 1, that could be exploited to vectorize many things while proposing a solution. Here's one such implementation -
%// Form new vectors out of input vectors for non-zero elements in vec1
vec1n = vec1(vec1~=0)
vec2n = vec2(vec1~=0)
%// Find positions of group shifts and length of groups
df1 = diff(vec1n)~=0
grp_change = [true df1]
grplen = find(df1,1)
%// Reshape vec2n, so that we end up with N x grplen sized array
vec2nr = reshape(vec2n,grplen,[]).' %//'
%// ID/tag each group change based on their unique vector 2 values
[R,C] = sort(vec1n(grp_change))
%// Re-arrange rows of reshaped vector2, s.t. same ID rows are grouped succesively
vec2nrs = vec2nr(C,:)
%// Find extents of each group & use those extents to have final cell array output
grp_extent = diff(find([1 diff(R) 1]))
out = mat2cell(vec2nrs,grp_extent,grplen)
Sample run for the given inputs -
>> vec1
vec1 =
0 0 1 1 1 0 0 2 2 2 ...
0 0 1 1 1 0 0 2 2 2
>> vec2
vec2 =
6 7 5 6 4 6 5 9 8 7 ...
9 7 0 5 6 4 1 5 8 0
>> celldisp(out)
out{1} =
5 6 4
0 5 6
out{2} =
9 8 7
5 8 0
Here is another solution:
v1 = [0 0 1 1 1 0 0 2 2 2 0 0 1 1 1 0 0 2 2 2];
v2 = [6 7 5 6 4 6 5 9 8 7 9 7 0 5 6 4 1 5 8 0];
m1 = reshape(v2(v1 == 1), 3, [])'
m2 = reshape(v2(v1 == 2), 3, [])'
EDIT: David's solution is more flexible and probably more efficient.

Greatest values in a matrix, row by row - matlab

I have an m-by-n matrix. For each row, I want to find the position of the k greatest values, and set the others to 0.
Example, for k=2
I WANT
[1 2 3 5 [0 0 3 5
4 5 9 3 0 5 9 0
2 6 7 1] 0 6 7 0 ]
You can achieve it easily using the second output of sort:
data = [ 1 2 3 5
4 5 9 3
2 6 7 1 ];
k = 2;
[M N] = size(data);
[~, ind] = sort(data,2);
data(repmat((1:M).',1,N-k) + (ind(:,1:N-k)-1)*M) = 0;
In the example, this gives
>> data
data =
0 0 3 5
0 5 9 0
0 6 7 0
You can use prctile command to find the threshold per-line.
prctile returns percentiles of the values in the rows of data and thus can be easily tweaked to return the threshold value above which the k-th largest elements at each row exist:
T = prctile( data, 100*(1 - k/size(data,2)), 2 ); % find the threshold
out = bsxfun(#gt, data, T) .* data; % set lower than T to zero
For the data matrix posted in the question we get
>> out
out =
0 0 3 5
0 5 9 0
0 6 7 0