Find number of consecutive elements before value changes (MATLAB) - matlab

I have a (row)vector of some size, containing the values 1,2 and 3. They are in there in no 'specific' order, so a sample of the array would be [1,1,1,1,2,2,2,1,1,2,2,3,3]. What I want to do is find the consecutive number of identical elements, but with some restrictions.
What I want is to make new arrays of which the elements denote:
The number of consecutive 1's before it changes into a 2
The number of consecutive 2's before it changes into a 1
The number of consecutive 2's before it changes into a 3
The number of consecutive 3's before it changes into a 2
So for the example I have given, the arrays would be
[4,2]
[3]
[2]
[]
I'm not sure how to tackle this. I can use the diff function to find where it changes sign, but then it'll be a little tough to figure out exactly what change has occured, right?
The method does not have to be super fast, as I only have to do this a few times for around 10^5 datapoints.

This approach will group things the way you specified in the question:
a=[1,1,1,1,2,2,2,1,1,2,2,3,3]
b = diff(a)
c = find(b)
d = diff([0,c]);
type1 = d(b(c) == 1 & a(c) == 1);
type2 = d(b(c) == -1 & a(c) == 2);
type3 = d(b(c) == 1 & a(c) == 2);
type4 = d(b(c) == -1 & a(c) == 3);
type1 =
4 2
type2 =
3
type3 =
2
type4 =
Empty matrix: 1-by-0

Use the standard procedure with diff to detect changes and run lengths, and then apply accumarray to group run lengths according to each pair of values before and after the change:
x = [1,1,1,1,2,2,2,1,1,2,2,3,3];
x = x.'; %'// easier to work with a column vector
ind = find(diff(x))+1; %// index of positions where x changes
%// To detect what change has ocurred, we'll use x(ind-1) and x(ind)
len = diff([1; ind]); %// length of each run
result = accumarray([x(ind-1) x(ind)], len, [], #(v) {v}); %// group lengths
Note the order within each result vector may be altered, as per accumarray.
In your example, this gives
>> result
result =
[] [2x1 double] []
[3] [] [2]
>> result{1,2}
ans =
2
4
>> result{2,1}
ans =
3
>> result{2,3}
ans =
2

I believe this will do the trick (although it's not very pretty)
a=[1,1,1,1,1,2,2,2,2,1,1,1,2,2,3,3,3];
d=diff(a);
deltas=(d~=0);
d12=[];d23=[];d32=[];d21=[];
last=0;
for i=1:length(a)-1
if deltas(i)
if a(i)==1&&a(i+1)==2
d12=[d12,i-last];
last=i;
elseif a(i)==2&&a(i+1)==3
d23=[d23,i-last];
last=i;
elseif a(i)==3&&a(i+1)==2
d32=[d32,i-last];
last=i;
elseif a(i)==2&&a(i+1)==1
d21=[d21,i-last];
last=i;
end
end
end

Related

Simplify a Matlab code involving finding the position of maximum elements of array

I would like your help to make more efficient (maybe, by vectorising) the Matlab code below. The code below does essentially the following: take a row vector A; consider the maximum elements of such a row vector and let, for example, be i and j their positions; construct two columns vectors, the first with all zeros but a 1 positioned at i, the second with all zeros but a 1 positioned at j.
This is my attempt with loops, but it looks more complicated than needed.
clear
rng default
A=[3 2 3];
max_idx=ismember(A,max(A));
vertex=cell(size(A,2),1);
for j=1:size(max_idx,2)
if max_idx(j)>0
position=find(max_idx(j));
vertex_temp=zeros(size(A,2),1);
vertex_temp(position)=1;
vertex{j}=vertex_temp;
else
vertex{j}=[];
end
end
vertex=vertex(~cellfun('isempty',vertex));
Still using a for loop, but more readable:
A = [3 2 3];
% find max indices
max_idx = find(A == max(A));
vertex = cell(numel(max_idx),1);
for k = 1:numel(max_idx)
vertex{k} = zeros(size(A,2),1); % init zeros
vertex{k}(max_idx(k)) = 1; % set value in vector to 1
end
If you really wanted to avoid a for loop, you could probably also use something like this:
A=[3 2 3];
max_idx = find(A==max(A));
outMat = zeros(numel(A), numel(max_idx));
outMat((0:(numel(max_idx)-1)) * numel(A) + max_idx) = 1;
then optionally, if you want them in separate cells rather than columns of a matrix:
outCell = mat2cell(outMat, numel(A), ones(1,numel(max_idx)))';
However, I think this might be less simple and readable than the existing answers.
Is there a specific reason you want a cell array rather than a matrix?
If you can have it all in one vector:
A = [3 2 3]
B_rowvec = A == max(A)
B_colvec = B_rowvec'
If you need them separated into separate vectors:
A = [3 2 3]
Nmaxval = sum(A==max(A))
outmat = zeros(length(A),Nmaxval)
for i = 1:Nmaxval
outmat(find(A==max(A),i),i)=1;
end
outvec1 = outmat(:,1)
outvec2 = outmat(:,2)
Basically, the second input for find will specify which satisfactory instance of the first input you want.
so therefore
example = [ 1 2 3 1 2 3 1 2 3 ]
first = find(example == 1, 1) % returns 1
second = find(example == 1, 2) % returns 4
third = find(example == 1, 3) % returns 7

How to check if cell's elements contain matrix

I have a cell described as the following:
mixed_values = {'jim', 89, [5 2 1; 1 2 3]};
mixed_values{1}
mixed_values{2}
mixed_values{3}
I loop it:
for k=1:length(mixed_values)
curstate=mixed_values{k};
% Check for the [5 2 1; 1 2 3]
if ismatrix(curstate)
disp('yes');
else
disp('no')
end
end
But it founds the matrix multiple times.
yes
yes
yes
How to check it by the way?
From Matlab help:
ismatrix(M) returns logical 1 (true) if SIZE(M) returns [m n] with
nonnegative integer values m and n, and logical 0 (false) otherwise
so I checked size(curstate)
1 3 % 3 character string array
1 1 % of course you can do size of a single elements
2 3
so I modified your code
for k=1:length(mixed_values)
curstate=mixed_values{k};
% Check for the [5 2 1; 1 2 3]
if (size(curstate,1)) > 1 && (size(curstate,2)) > 1
disp('yes');
disp(size(curstate));
else
disp('no')
end
end
It really depends on what you define a matrix to be. In MathWorks case they decided that a matrix would be something with a valid size, which is certainly true. Notice that even scalars are matrices, of size 1x1. You can even have a matrix of characters. A = ['a' 'b';'c' 'd'];. I gather that in your case you want a matrix to be a numerical collection of at least 2 dimensions. I would solve it this way:
function result = TestForMatrix(m)
t1 = isnumeric(m);
t2 = ~isvector(m);
result = all([t1 t2]);
end
Use it as if it was ismatrix.
if (TestForMatrix(curstate))
disp('yes');
else
....
The way this works is the test for numerical numbers will eliminate character strings. The second test will eliminate vectors and scalars. As you find more things to include or eliminate you add those tests. For example, say you want to allow cells. t3 = iscell(m); result = all([t1 t2 t3]); The are many logical tests that can be done on Matlab objects, see Matlab is*.

Shifting repeating rows to a new column in a matrix

I am working with a n x 1 matrix, A, that has repeating values inside it:
A = [0;1;2;3;4; 0;1;2;3;4; 0;1;2;3;4; 0;1;2;3;4]
which correspond to an n x 1 matrix of B values:
B = [2;4;6;8;10; 3;5;7;9;11; 4;6;8;10;12; 5;7;9;11;13]
I am attempting to produce a generalised code to place each repetition into a separate column and store it into Aa and Bb, e.g.:
Aa = [0 0 0 0 Bb = [2 3 4 5
1 1 1 1 4 5 6 7
2 2 2 2 6 7 8 9
3 3 3 3 8 9 10 11
4 4 4 4] 10 11 12 13]
Essentially, each repetition from A and B needs to be copied into the next column and then deleted from the first column
So far I have managed to identify how many repetitions there are and copy the entire column over to the next column and then the next for the amount of repetitions there are but my method doesn't shift the matrix rows to columns as such.
clc;clf;close all
A = [0;1;2;3;4;0;1;2;3;4;0;1;2;3;4;0;1;2;3;4];
B = [2;4;6;8;10;3;5;7;9;11;4;6;8;10;12;5;7;9;11;13];
desiredCol = 1; %next column to go to
destinationCol = 0; %column to start on
n = length(A);
for i = 2:1:n-1
if A == 0;
A = [ A(:, 1:destinationCol)...
A(:, desiredCol+1:destinationCol)...
A(:, desiredCol)...
A(:, destinationCol+1:end) ];
end
end
A = [...] retrieved from Move a set of N-rows to another column in MATLAB
Any hints would be much appreciated. If you need further explanation, let me know!
Thanks!
Given our discussion in the comments, all you need is to use reshape which converts a matrix of known dimensions into an output matrix with specified dimensions provided that the number of elements match. You wish to transform a vector which has a set amount of repeating patterns into a matrix where each column has one of these repeating instances. reshape creates a matrix in column-major order where values are sampled column-wise and the matrix is populated this way. This is perfect for your situation.
Assuming that you already know how many "repeats" you're expecting, we call this An, you simply need to reshape your vector so that it has T = n / An rows where n is the length of the vector. Something like this will work.
n = numel(A); T = n / An;
Aa = reshape(A, T, []);
Bb = reshape(B, T, []);
The third parameter has empty braces and this tells MATLAB to infer how many columns there will be given that there are T rows. Technically, this would simply be An columns but it's nice to show you how flexible MATLAB can be.
If you say you already know the repeated subvector, and the number of times it repeats then it is relatively straight forward:
First make your new A matrix with the repmat function.
Then remap your B vector to the same size as you new A matrix
% Given that you already have the repeated subvector Asub, and the number
% of times it repeats; An:
Asub = [0;1;2;3;4];
An = 4;
lengthAsub = length(Asub);
Anew = repmat(Asub, [1,An]);
% If you can assume that the number of elements in B is equal to the number
% of elements in A:
numberColumns = size(Anew, 2);
newB = zeros(size(Anew));
for i = 1:numberColumns
indexStart = (i-1) * lengthAsub + 1;
indexEnd = indexStart + An;
newB(:,i) = B(indexStart:indexEnd);
end
If you don't know what is in your original A vector, but you do know it is repetitive, if you assume that the pattern has no repeats you can use the find function to find when the first element is repeated:
lengthAsub = find(A(2:end) == A(1), 1);
Asub = A(1:lengthAsub);
An = length(A) / lengthAsub
Hopefully this fits in with your data: the only reason it would not is if your subvector within A is a pattern which does not have unique numbers, such as:
A = [0;1;2;3;2;1;0; 0;1;2;3;2;1;0; 0;1;2;3;2;1;0; 0;1;2;3;2;1;0;]
It is worth noting that from the above intuitively you would have lengthAsub = find(A(2:end) == A(1), 1) - 1;, But this is not necessary because you are already effectively taking the one off by only looking in the matrix A(2:end).

Shuffle a vector of repeated numbers so the numbers do not repeat in MATLAB

Okay, so I have a script that will produce my vector of repeated integers of a certain interval, but now theres a particular instance where I need to make sure that once it is shuffled, the numbers do not repeat. So for example, I produced a vector of repeating 1-5, 36 times, shuffled. How do I ensure that there are no repeated numbers after shuffling? And to make things even more complex, I need to produce two such vectors that do not ever have the same value at the same index. For example, lets say 1:5 was repeated twice for these vectors, so then this would be what I'm looking for:
v1 v2
4 2
2 4
3 2
5 3
4 5
1 4
5 1
1 5
3 1
2 3
I made that right now by taking an example of 1 vector and just shifting it off by 1 to create another vector that will satisfy the requirements, but in my situation, that wont actually work because I can't have them be systematically dependent like that.
So I tried a recursive technique to make the script start over if the vectors did not make the cut and as expected, that did not go over so well. I hit my maximum recursive iterations and I've realized this is clearly not the way to go. Is there some other alternative?
EDIT:
So I found a way to satisfy some of the conditions I needed above in the following code:
a = nchoosek(1:5,2);
b = horzcat(a(:,2),a(:,1));
c = vertcat(a,b);
cols = repmat(c,9,1);
cols = cols(randperm(180),:);
I just need to find a way to shuffle cols that will also enforce no repeating numbers in columns, such that cols(i,1) ~= cols(i+1,1) and cols(i,2) ~= cols(i+1,2)
This works, but it probably is not very efficient for a large array:
a = nchoosek(1:5, 2);
while (any(a(1: end - 1, 1) == a(2: end, 1)) ...
|| any(a(1: end - 1, 2) == a(2: end, 2)))
random_indices = randperm(size(a, 1));
a = a(random_indices, :);
end
a
If you want something faster, the trick is to logically insert each row in a place where your conditions are satisfied, rather than randomly re-shuffling. For example:
n1 = 5;
n2 = 9;
a = nchoosek(1:n1, 2);
b = horzcat(a(:,2), a(:,1));
c = vertcat(a, b);
d = repmat(c, n2, 1);
d = d(randperm(n1 * n2), :);
% Perform an "insertion shuffle"
for k = 2: n1 * n2
% Grab row k from array d. Walk down the rows until a position is
% found where row k does not repeat with its upstairs or downstairs
% neighbors.
m = 1;
while (any(d(k,:) == d(m,:)) || any(d(k,:) == d(m+1,:)))
m = m + 1;
end
% Insert row k in the proper position.
if (m < k)
ind = [ 1: m k m+1: k-1 k+1: n1 * n2 ];
else
ind = [ 1: k-1 k+1: m k m+1: n1 * n2 ];
end
d = d(ind,:);
end
d
One way to solve this problem is to think both vectors as being created as follows:
For every row of arrays v1 and v2
Shuffle the array [1 2 3 4 5]
Set the values of v1 and v2 at the current row with the first and second value of the shuffle. Both values will always be different.
Code:
s = [1 2 3 4 5];
Nrows = 36;
solution = zeros(Nrows,2);
for k=1:Nrows
% obtain indexes j for shuffling array s
[x,j] = sort(rand(1,5));
%row k takes the first two values of shuffled array s
solution(k,1:2) = s(j(1:2));
end
v1 = solution(:,1);
v2 = solution(:,2);
Main edit: random => rand,
With this method there is no time wasted in re-rolling repeated numbers because the first and second value of shuffling [1 2 3 4 5] will always be different.
Should you need more than two arrays with different numbers the changes are simple.

Indices of constant consecutive values in a matrix, and number of constant values

I have a matrix with constant consecutive values randomly distributed throughout the matrix. I want the indices of the consecutive values, and further, I want a matrix of the same size as the original matrix, where the number of consecutive values are stored in the indices of the consecutive values. For Example
original_matrix = [1 1 1;2 2 3; 1 2 3];
output_matrix = [3 3 3;2 2 0;0 0 0];
I have struggled mightily to find a solution to this problem. It has relevance for meteorological data quality control. For example, if I have a matrix of temperature data from a number of sensors, and I want to know what days had constant consecutive values, and how many days were constant, so I can then flag the data as possibly faulty.
temperature matrix is number of days x number of stations and I want an output matrix that is also number of days x number of stations, where the consecutive values are flagged as described above.
If you have a solution to that, please provide! Thank you.
For this kind of problems, I made my own utility function runlength:
function RL = runlength(M)
% calculates length of runs of consecutive equal items along columns of M
% work along columns, so that you can use linear indexing
% find locations where items change along column
jumps = diff(M) ~= 0;
% add implicit jumps at start and end
ncol = size(jumps, 2);
jumps = [true(1, ncol); jumps; true(1, ncol)];
% find linear indices of starts and stops of runs
ijump = find(jumps);
nrow = size(jumps, 1);
istart = ijump(rem(ijump, nrow) ~= 0); % remove fake starts in last row
istop = ijump(rem(ijump, nrow) ~= 1); % remove fake stops in first row
rl = istop - istart;
assert(sum(rl) == numel(M))
% make matrix of 'derivative' of runlength
% don't need last row, but needs same size as jumps for indices to be valid
dRL = zeros(size(jumps));
dRL(istart) = rl;
dRL(istop) = dRL(istop) - rl;
% remove last row and 'integrate' to get runlength
RL = cumsum(dRL(1:end-1,:));
It only works along columns since it uses linear indexing. Since you want do something similar along rows, you need to transpose back and forth, so you could use it for your case like so:
>> original = [1 1 1;2 2 3; 1 2 3];
>> original = original.'; % transpose, since runlength works along columns
>> output = runlength(original);
>> output = output.'; % transpose back
>> output(output == 1) = 0; % see hitzg's comment
>> output
output =
3 3 3
2 2 0
0 0 0