How to find maximum value within multiple intervals? - matlab

I have two arrays, der_pos and der_neg, which contain indices of an array interpolated. I want to get all the indices for the maximum values of interpolated in the intervals:
der_pos(1):der_neg(1)
der_pos(2):der_neg(2)
etc...
E.g.:
interpolated = [1,5,3,2,7,10,8,14,4]
der_pos = [1,6]
der_neg = [4,9]
So, I would like to obtain the indices:
[2,8]
Because:
in the interval der_pos(1):der_neg(1) → 1:4 → interpolated(1:4) = [1,5,3,2] the max is 5 which is at index 2.
in the interval der_pos(2):der_neg(2) → 6:9 → interpolated(6:9) = [10,8,14,4] the max is 14 which is at index 8.
I managed to do it using a for loop:
interpolated = [1,5,3,2,7,10,8,14,4];
der_pos = [1,6];
der_neg = [4,9];
indices = zeros(1,length(der_pos));
for i = [1:length(der_pos)]
[peak, index] = max(interpolated(der_pos(i):der_neg(i)));
indices(i) = index + der_pos(i) - 1;
endfor
indices % gives [2,8]
But is there a more concise way of doing this?

Here's a sample code. The function findpeaks returns all the peak. The loop then keeps indices of peaks that are in the wanted range.
I added a test to avoid errors in case of no found peak (index will be -1), and to keep the first peak if two peaks are found. You can use a cell if you want to keep all peaks in an interval.
interpolated = [1,5,3,2,7,10,8,14,4];
der_pos = [1 6 7 ];
der_neg = [4 9 8];
[~,i]=findpeaks(interpolated);
indices= -1+zeros(size(der_pos,2),1);
for loopi = 1:length(i)
val = i(i>=der_pos(loopi)&i<=der_neg(loopi));
if ~isempty(val)
indices(loopi) = val(1);
end
end

Here's a way:
interpolated = [1,5,3,2,7,10,8,14,4]; % data
der_pos = [1,6]; % data
der_neg = [4,9]; % data
m = bsxfun(#ge, 1:numel(interpolated), der_pos(:)) .* ...
bsxfun(#le, 1:numel(interpolated), der_neg(:)); % each row is a mask that contains
% 1 for values in the interval and 0 for values outside the interval
m(~m) = NaN; % replace 0 by NaN
[val, result] = max(bsxfun(#times, m, interpolated(:).'), [], 2); % val is the maximum
% of each row, and result is its column index. The maximum is 1 for rows that
% contain at least a 1, and NaN for rows that only contain NaN
result(isnan(val)) = 0; % If the maximum value was NaN the result is set to 0
% (or maybe use NaN), to indicate that the interval was empty
This gives 0 for empty intervals. For example, der_pos = [1,6,8]; der_neg = [4,9,6]; produce result = [2;8;0].
The intervals may overlap. For example, der_pos = [1,6,3]; der_neg = [4,9,7]; produce result = [2;8;6].

Related

Matlab get all possible combinations less than a value

I have a matrix as follows:
id value
=============
1 0.5
2 0.5
3 0.8
4 0.3
5 0.2
From this array, I wish to find all the possible combinations that have a sum less than or equal to 1. That is,
result
======
1 2
1 4 5
2 4 5
3 5
1 5
1 4
2 4
2 5
...
In order to get the above result, my idea has been to initially compute all the possibilities of finding sum of elements in the array, like so:
for ii = 1 : length(a) % compute number of possibilities
no_of_possibilities = no_of_possibilities + nchoosek(length(a),ii);
end
Once this is done, then loop through all possible combinations.
I would like to know if there's an easier way of doing this.
data = [0.5, 0.5, 0.8, 0.3, 0.2];
required = cell(1, length(data));
subsets = cell(1, length(data));
for k = 2:length(data)-1 % removes trivial cases (all numbers or one number at a time)
% generate all possible k-pairs (if k = 3, then all possible triplets
% will be generated)
combination = nchoosek(1:length(data), k);
% for every triplet generated, this function sums the corresponding
% values and then decides whether then sum is less than equal to 1 or
% not
findRequired = #(x) sum(data(1, combination(x, :))) <= 1;
% generate a logical vector for all possible combinations like [0 1 0]
% which denotes that the 2nd combination satisfies the condition while
% the others do not
required{k} = arrayfun(findRequired, 1:size(combination, 1));
% access the corresponding combinations from the entire set
subsets{k} = combination(required{k}, :);
end
This produces the following subsets:
1 2
1 4
1 5
2 4
2 5
3 5
4 5
1 4 5
2 4 5
It is not in easy way, however is a faster way, as I removed the combination which its subsets are not passed the condition.
bitNo = length(A); % number of bits
setNo = 2 ^ bitNo - 1; % number of sets
subsets = logical(dec2bin(0:setNo, bitNo) - '0'); % all subsets
subsets = subsets(2:end,:); % all subsets minus empty set!
subsetCounter = 1;
resultCounter = 1;
result = {};
while(1)
if( subsetCounter >= size(subsets,1))
break;
end
if(sum(A(subsets(subsetCounter,:).',2)) <= 1)
result{resultCounter} = A(subsets(subsetCounter,:).',1).';
resultCounter = resultCounter + 1;
subsetCounter = subsetCounter + 1;
else
% remove all bad cases related to the current subset
subsets = subsets(sum((subsets & subsets(subsetCounter,:)) - subsets(subsetCounter,:),2) ~= 0,:);
end
end
Generate the subsets using this method. After that, check the condition for each subset. If the subset does not pass the condition, all its supersets are removed from the subsets. To do this, using sum((subsets & subsets(i,:)) - subsets(i,:),2) ~= 0 which mean get some rows from subsets which has not the same elements of the not passed subset. By doing this, we able to not to consider some bad cases anymore. Although, theoretically, this code is Θ(2^n).
Here is potential solution, using inefficient steps, but borrowing efficient code from various SO answers. Credit goes to those original peeps.
data = [0.5, 0.5, 0.8, 0.3, 0.2];
First get all combinations of indices, not necessarily using all values.
combs = bsxfun(#minus, nchoosek(1:numel(data)+numel(data)-1,numel(data)), 0:numel(data)-1);
Then get rid of repeated indices in each combination, regardless of index order
[ii, ~, vv] = find(sort(combs,2));
uniq = accumarray(ii(:), vv(:), [], #(x){unique(x.')});
Next get unique combinations, regardless of index order... NOTE: You can do this step much more efficiently by restructuring the steps, but it'll do.
B = cellfun(#mat2str,uniq,'uniformoutput',false);
[~,ia] = unique(B);
uniq=uniq(ia);
Now sum all values in data based on cell array (uniq) of index combinations
idx = cumsum(cellfun('length', uniq));
x = diff(bsxfun(#ge, [0; idx(:)], 1:max(idx)));
x = sum(bsxfun(#times, x', 1:numel(uniq)), 2); %'// Produce subscripts
y = data([uniq{:}]); % // Obtain values
sums_data = accumarray(x, y);
And finally only keep the index combinations that sum to <= 1
allCombLessThanVal = uniq(sums_data<=1)

Smallest N elements of an array with their location

I have an array called Acc_Std of size 1 Row and 222 Columns.
I need to have the smallest 100 values in each that array but with their original location.
I have written this code but, actually, doesn't work:
for Col = 1:222
[Std_Cont, Std_Loc] = min(Acc_Std(:));
Sort_Std_Cont(Col,1) = Std_Cont;
Sort_Std_Loc(Col,1) = Std_Loc;
Acc_Std(Std_Loc) = []; % Here is the problem in my code
end
Use both outputs of sort:
% Example data
Acc_Std = randi(10, 1,10);
% Extract the smallest N elements
N = 3;
% Sort, while saving the original indices
[B, indices] = sort(Acc_Std);
% Now extract the N smallest elements
smallest_N = B(1:N);
% Check that they are indeed located at the
% indices returned by sort()
isequal(smallest_N, Acc_Std(indices(1:N)))
Result of executing this little script:
ans =
1

Get the first four minimum values in a matrix

I have a matrix:
X =
0 81 13 15 100 2
11 0 6 10 200 8
19 22 0 20 300 23
I want to get the first four minimal values in the whole array X with the indices of each value in the array. For example I should get vector v = [2 6 8 10] and the index of each value in X.
Also, I want to ignore the zero values when the row number equals the column number.
I have tried to use the min and sort functions, but I am not sure how to do it.
I would suggest the following
X2 = X;
X2(~~eye(size(X2))) = inf; %// or X2(logical(eye(size(X2)))) = inf
[val, idx] = sort(X2(:));
result = val(1:4);
[idxRow, idxCol] = ind2sub(size(X), idx(1:4));
Use:
vals = sort(X(~eye(size(X)))); %takes non diagonal values and sort the result
res = vals(1:4) %finds the first 4 elements (which are the smallest)
[row, col] = find(ismember(X,res)); %gets the indices
result:
res = [2; 6; 8; 10]
By The way, if you don't want to ignore all the diagonal values, only the zero ones, use:
vals = sort(X(~eye(size(X)) | (eye(size(X)) & X~=0)));
Sort all but the ones on the diagonal and then find the indices of the ones which are smaller than or equal to the 4th element of sorted array and not on the diagonal:
T=sort(X(~eye(size(X))));
v = T(1:4);
[I,J] = find(X <= v(end) & ~eye(size(X)));
Just want to add to drorco's perfect answer how to find indexes of this first elements:
indexes = arrayfun( #(a) find(X==a), res);
or if you want to get numbers of rows and columns:
[r,c] = arrayfun( #(a) find(X==a), res);
P.S. it works perfectly if all elements except zeros in X are unique.

Replicate vector and shift each copy by 1 row down without for-loop

I would like replicate a vector N times to create a matrix with each copy shifted 1 row down. See image (first column is the vector 1 to 5). It would be great if this can be achieved without using for loop.
So far was able to to do this repmat(my_vector, 1, 5) to create an N x 5 matrix.
You can do it with toeplitz and tril;
a = [1 2 3 4 5]
out = tril( toeplitz(a) )
or
out = toeplitz(a, a*0)
%// out = toeplitz(a, zeros(size(a)) ) %// for large arrays
or if you don't mind some happy flipping:
out = flipud( hankel( flipud(a(:)) ) )
Solution Code
This seems to be a fast approach based on repmat and bsxfun as the benchmarks listed in the next section might convince us -
%// Concatenate one zero at the end of a column vector version of the input vector.
%// Then, replicate the whole vector along columns to have a 2D matrix.
%// Then "progressively" set elements from each column as zeros corresponding
%// to the starting zeros of the desired output.
val = repmat([A(:);0],1,N).*bsxfun(#le,[1:N+1]',N:-1:1); %//'
%// Chop-off at N x N length and reshape to have the final output
out = reshape(val(1:N*N),N,N);
Benchmarking
In this section we will cover runtime benchmarking for the various approaches listed on this page for the stated problem.
Benchmarking Code -
%datasizes = [10 20 50 70 100 200 500 700 1000]; %// Set -1
datasizes = [1000 2000 5000 7000 10000]; %// Set -2
fcns = {'repvecshiftdown_flipud_hankel','repvecshiftdown_toeplitz',...
'repvecshiftdown_repmat_bsxfun','repvecshiftdown_tril_toeplitz'};%//approaches
tsec = zeros(numel(fcns),numel(datasizes));
for k1 = 1:numel(datasizes),
A = randi(9,1,datasizes(k1)); %// Creare random input vector
for k2 = 1:numel(fcns), %// Time approaches
tsec(k2,k1) = timeit(#() feval(fcns{k2}, A), 1);
fprintf('\tFunction: %s (%3.2f sec)\n',fcns{k2},tsec(k2,k1));
end
end
figure; %% Plot Runtimes
plot(datasizes,tsec(1,:),'-rx'), hold on
plot(datasizes,tsec(2,:),'-bo')
plot(datasizes,tsec(3,:),'-k+')
plot(datasizes,tsec(4,:),'-g.')
set(gca,'xgrid','on'),set(gca,'ygrid','on'),
xlabel('Datasize (# elements)'), ylabel('Runtime (s)')
legend(upper(strrep(fcns,'_',' '))),title('Runtime')
Associated function codes (all approaches) -
function out = repvecshiftdown_repmat_bsxfun(A)
N = numel(A);
val = repmat([A(:);0],1,N).*bsxfun(#le,[1:N+1]',[N:-1:1]); %//'
out = reshape(val(1:N*N),N,N);
return;
function out = repvecshiftdown_tril_toeplitz(A)
out = tril( toeplitz(A) );
return;
function out = repvecshiftdown_toeplitz(A)
out = toeplitz(A, zeros(size(A)));
return;
function out = repvecshiftdown_flipud_hankel(A)
out = flipud( hankel( flipud(A(:)) ) );
return;
Runtime plots -
Set #1 [From 10 till 1000 datasizes]:
Set #2 [From 1000 till 10000 datasizes]:

How to adjust result of randoming

I have a problem adjusting the result of number randoming,
jum_k = 14;
jum_b = 12;
result = randint(jum_k, jum_b, [0 2]);
so that there is a constraint on the final result. There should not be a value "0" appearing more than three times in row.
Your random entries are then non-uniformly distributed with unknown weights, i.e. the number of zero per line can be <=3 ([0, 1, 2, 3]). I would hack around it this way: Populate an [m x n] matrix uniformly in [1,2], choose (random) number of zeros per line, then choose (random) their locations. Example:
jum_k = 14;
jum_b = 12;
result = randi([1, 2], jum_k, jum_b);
for i = 1:jum_k
nZeros = randi([0, 3]); % number of zeros (random)
result(i, randi(jum_b, 1, nZeros)) = 0; % locations in line (random)
end;
If you need an exact number of zeros per line you can modify accordingly.
EDIT (after clarifications on question from comments): To accomodate for no more than 3 zeros in sequence per each line, e.g. [1,0,0,0...2] you can populate the matrix element-wise and check for the pattern [0,0,0,0] in previous elements (keeping a buffer of previous values).
result = nan(jum_k, jum_b); % intitialize
for i = 1:jum_k
for j = 1:jum_b
result(i, j) = randi([0, 2]); % assign value
if j>3 && ~all(result(i, j-3:j)) % check previous values
result(i, j-randi([0, 3])) = randi([1, 2]); % randomly change one
end
end
end
%% check/test that all lines have less 4 zeros in sequence
f = #strfind;
for i = 1:jum_k
t(i) = isempty(f(result(i,:),[0 0 0 0]));
end
T = all(t);
It's not optimal (MATLAB-wise) but will do the job.