Rearrange rows according to common column element - matlab

Give for example the matrices
A = [20 1 2 3;
3 3 3 4];
B = [3 3 3 3;
20 1 2 4];
Each column of matrix A has a common element. Is it possible without for loops to rearrange rows of A so as the common element to be in the top or bottom row (see matrix B)?

I suggest you use the Set functions with multiple inputs file exchange submission by Oleg to find the common element. First convert A to a cell array of column vectors using mat2cell. Then break it up into a comma separated list using the {:} notation to feed each column to intersectm (from the FEX entry linked to above) as separate inputs
A_cell = mat2cell(A,2,ones(1,size(A,2)));
common = intersectm(A_cell{:});
now find which row the common element lies in per column and use linear indexing to flip the columns in which the common element is in the second row
[r, c] = find(A == common);
idx_r = (r+c*2-2)';
idx = idx_r;
idx(2,:) = (idx_r-1).*~mod(idx_r,2) + (idx_r+1).*mod(idx_r,2);
Finally
B = A(idx)

Related

Indexing rows of a table by comparing the values between two cells

I have a table like the above attachment. Column A and Column B contains some elements in terms of cell array. I want to create the third column (Level) as the resultant column; based on the following logic.
The row for which, value of cell A = value of cell B will be labeled1. (In the 3rd row, the value of column A=value of column B= 3, hence labeled 1).
Next, the preceding value will be removed from all
the cells of column A; and the step 1 will be repeated until all the
rows are labeled. (In the second step, 3 will be removed from all
the cells, hence both row 1 and row 2 will be labeled as 2;In the
final step, elements {1,2} will be further removed from the last row
resulting the level as 3 )
I am using cell2mat and setdiff functions to compare the values across the cells, but I am not able to frame the above 2 logical steps to run my code successfully. I have just started learning MATLAB, Any help will be highly appreciated.
Here's the simplest answer I could come up with, using a single while loop and assuming the cells of A and B contain row vectors:
Level = zeros(size(A));
index = cellfun(#isequal, A, B);
while any(index)
Level(index) = max(Level)+1;
A = cellfun(#(c) {setdiff(c, unique([A{index}]))}, A);
index = cellfun(#isequal, A, B);
end
The above code first initializes a matrix of zeroes Level the same size as A to store the level values. Then it finds a logical index index of where there are matching cell contents between A and B using cellfun and isequal. It will continue to loop as long as there are any matches indicated by index. The corresponding indices in Level are set to the current maximum value in Level plus one. All the matching cell contents from A are concatenated and the unique values found by unique([A{index}]). A set difference operation is then used (along with cellfun) to remove the matching values from each cell in A, overwriting A with the remaining values. A new index for matches is then computed and the loop restarts.
Given the following sample data from your question:
A = {[1 2 3]; [2 3]; 3; [1 2 3 4]};
B = {[1 2]; 2; 3; 4};
The code returns the expected level vector:
Level =
2
2
1
3
Not my best work, i think it is possible to get rid of the inner loop.
% your testdata
A = {[1 2 3]
[2 3]
3
[1,2,4]};
B = {[1 2]
2
3
4};
Level = NaN(numel(B),1);
temp = A; % copy of A that we are going to remove elements from
k = 0; % loop couter
while any(isnan(Level)) % do until each element of Level is not NaN
k = k+1; % increment counter by 1
% step 1
idx = find(cellfun(#isequal,temp,B)); % determine which cells are equal
Level(idx) = k; % set level of equal cells
% step 2
for k = 1:numel(idx) % for each cell that is equal
%remove values in B from A for each equal cell
temp = cellfun(#setdiff,temp,repmat(B(idx(k)),numel(B),1),'UniformOutput',0);
end
end

Matlab find unique column-combinations in matrix and respective index

I have a large matrix with with multiple rows and a limited (but larger than 1) number of columns containing values between 0 and 9 and would like to find an efficient way to identify unique row-wise combinations and their indices to then build sums (somehwat like a pivot logic). Here is an example of what I am trying to achieve:
a =
1 2 3
2 2 3
3 2 1
1 2 3
3 2 1
uniqueCombs =
1 2 3
2 2 3
3 2 1
numOccurrences =
2
1
2
indizies:
[1;4]
[2]
[3;5]
From matrix a, I want to first identify the unique combinations (row-wise), then count the number occurrences / identify the row-index of the respective combination.
I have achieved this through generating strings with num2str and strcat, but this method appears to be very slow. Along these thoughts I have tried to find a way to form a new unique number through concatenating the values horizontally, but Matlab does not seem to support this (e.g. from [1;2;3] build 123). Sums won't work because they would remove the possibility to identify unique combinations. Any suggestions on how to best achieve this? Thanks!
To get the unique rows you can use unique with the 'rows' option enabled:
[C, ix, ic] = unique(a, 'rows', 'stable');
C contains the unique rows; ix the indexes of the first occurrences of those rows in C; ic basically contains the information you want. To access it you can loop over the indexes of ix and save them in a cell array:
indexes = cell(1, length(ix));
for k = 1:length(ix)
indexes{k} = find(ic == ix(k));
end
indexes will be a cell array containing the indexes you were looking for. For example:
indexes{1}
% ans =
%
% 1
% 4
And to count the occurrences of a particular combination you can just use numel. For example:
numel(indexes{1})
% ans =
%
% 2

How to sum aggregate matrix rows in the following way?

I have a matrix of size n-by-3. For some rows of this matrix, the first two columns are identical. I need to keep only one copy of these first-two-element combinations, where the third column will have the sum of 3rd column from rows with identical first-two-columns.
Here's an example of what I want to do:
M = [...
1 2 1
1 2 3
1 2 2
1 2 4
2 3 1
2 3 4
2 3 0];
The final matrix that I need is
R = [...
1 2 1+3+2+4
2 3 1+4+0];
How can this be done? I don't see how I can use the unique command for this.
You may use unique in combination with accumarray. Let's call the initial n x 3 array A:
[C, ~, ic] = unique(A(:,1:2), 'rows');
B = [C, accumarray(ic, A(:,3))];
Explanation:
unique outputs not only unique elements of array (rows in our case thanks to the argument rows), but also two arrays of indexes. The first one is the indexes of the first unique elements in A; I discard it since I don't use it. The second one can be used to reconstruct original array from the output array: A(:, 1:2) = C(ic,:).
accumarray is the generalization of histogram computation, it makes the sum of elements in 2nd argument array for each unique index in the first argument array. In your case, you make the sum over the 3rd column of the original array only.
And that's all in two simple commands!

Index a vector by a matrix of conditions to obtain multiple selections of the target?

I have a vector T of length n and m other vectors of the same length with 0 or 1 used as condition to select elements of T. The condition vectors are combined into a matrix I of size n x m.
Is there a one liner to extract a matrix M of values from Tsuch that the i-th column of M are those elements in T that are selected by the condition elements of the i-th column in I?
Example:
T = (1:10)'
I = mod(T,2) == 0
T(I)'
yields
2 4 6 8 10
However
I = mod(T,2:4) == 0
T(I)'
yields an error in the last statement. I see that the columns might select a different number of elements which results in vectors of different lengths (as in the example). However, even this example doesn't work:
I = zeros(10,2)
I(:,1) = mod(T,2)==0
I(:,2) = mod(T,2)==1
Is there any way to achieve the solution in a one liner?
The easiest way I can think of to do something like this is to take advantage of the element-wise multiplication operator .* with your matrix I. Take this as an example:
% these lines are just setup of your problem
m = 10;
n = 10;
T = [1:m]';
I = randi([0 1], m, n);
% 1 liner to create M
M = repmat(T, 1, n) .* I;
What this does is expand T to be the same size as I using repmat and then multiplies all the elements together using .*.
Here is a one linear solution
mat2cell(T(nonzeros(bsxfun(#times,I,(1:numel(T)).'))),sum(I))
First logical index should be converted to numeric index for it we multiply T by each column of I
idx = bsxfun(#times,I,(1:numel(T)).');
But that index contain zeros we should extract those values that correspond to 1s in matrix I:
idx = nonzeros(idx);
Then we extract repeated elements of T :
T2 = T(idx);
so we need to split T2 to 3 parts size of each part is equal to sum of elements of corresponding column of I and mat2cell is very helpful
result = mat2cell(T2,sum(I));
result
ans =
{
[1,1] =
2
4
6
8
10
[2,1] =
3
6
9
[3,1] =
4
8
}
One line solution using cellfun and mat2cell
nColumns = size(I,2); nRows = size(T,1); % Take the liberty of a line to write cleaner code
cellfun(#(i)T(i),mat2cell(I,nRows,ones(nColumns,1)),'uni',0)
What is going on:
#(i)T(i) % defines a function handle that takes a logical index and returns elements from T for those indexes
mat2cell(I,nRows,ones(nColumns,1)) % Split I such that every column is a cell
'uni',0 % Tell cellfun that the function returns non uniform output

Filtering a matrix by unique column elements

M is a matrix: M = [X Y Z] where X, Y and Z are column vectors.
What is the easiest way to filter M so that:
1- No element is repeated per column
2- The order of the rows remain (if an element appears twice in a column, then I want to delete the entire row where it appears the second time)
e.g:
M = [1 2 4;
1 3 5;
2 3 9]
would become
Mf = [1 2 4;
2 3 9]
I tried to use [u,~,ind] = unique(M,'rows') to have the elements for which one element in a column is repeated, but this function deals with the entire row (if only one element of the row is repeated, then the row is unique)
Here is a quick and dirty solution, should be fine as long as you M isn't too big. I've tested it on a few matrices and it seems to work as intended.
count=1;
for i=1:length(M(1,:))^2
[~,IA,~]=unique(M(:,count),'first');
if length(IA)~=length(M(:,1))
M=M(IA,:);
count=count-1;
end
count=count+1;
if count>length(M(:,1))
break
end
end
M