Logical index of structure with various dimensioned fields - matlab

Lets say I have a structure like this:
S.index = 1:10;
S.testMatrix = zeros(3,3,10);
for x = 1:10
S.testMatrix(:,:,x) = magic(3) + x;
end
S.other = reshape(0:39, 4, 10);
It contains a 1x10 vector, a 3x3x10 multi-paged array and a 4x10 matrix. Now say I want to select only the entries corresponding to the indices between 2 and 8. mask = S.index > 2 & S.index < 8;
I tried structfun(#(x) x(mask), S, 'UniformOutput', 0); first which correctly worked for only the vector, which makes perfect sense. So then I figured all I needed to do was expand my mask. So I did this.
test = structfun(#(x) x(repmat(mask, size(x, ndims(x) - 1), 1)), S, 'UniformOutput',0);
The expanded mask was correct for the matrix but not the multi-paged array. And the 2D matrix was flattened to a vector.
If I was going to index these elements individually I would do something like this:
S2.index = S.index(mask);
S2.other = S.other(:,mask);
S2.testMatrix = S.testMatrix(:,:,mask);
My use case is for hundreds of structures each with 20+ fields. How do I script the indexing? The exact problem occurs is limited to a structure with 1xN vectors, 3xN and 4xN matrices and 3x3xN arrays. The mask is constructed based on one of the vectors representing time. The field names are constant for each structure so I could brute force the thing and type in the commands and run it as a function, but I'm looking for an intelligent way to index it.
Update: Here is something that looks promising.
fn = fieldnames(S);
for x = 1:length(fn)
extraDim = repmat({':'}, 1, ndims(S.(fn{x})) - 1);
S2.(fn{x}) = S.(fn{x})(extraDim{:}, mask);
end

You can exploit the fact that the string ':' can be used as an index instead of :, and build a comma-separated list of that string repeated the appropriate number of times for each field:
s = {':',':'}; % auxilary cell array to generate the comma-separated list
S2 = structfun(#(f) f(s{1:ndims(f)-1}, mask), S, 'UniformOutput', false);

Related

How to sort all elements of a 2d matrix in MATLAB?

The sort() function sorts the elements row/column wise but how to sort the elements absolutely? The result should be another matrix with smallest element in (1,1) , second smallest in (1,2) and so on.
Take some random input
input = rand(5,10);
If you want the output to be a row vector, simply use
sortedRow = sort(input(:)).';
If you want the result to be the same shape as the input, then use
sortedOriginalShape = reshape(sort(input(:)), size(input,2), size(input,1)).';
Note that when maintaining the shape, we must use the reversed size dimensions and then transpose. This is because otherwise the result is column-wise i.e. smallest element in (1,1), next in (2,1) etc, which is the opposite of what you requested.
You can use the column operator (:) to vectorize all elements of 'nxm' matrix as a vector of 'nxm' elements and sort this vector. Then you can use direct assignement or 'reshape' function to store elements as matricial form.
All you need to know is that matlab use column-major-ordering to vectorize/iterate elements:
A = rand(3, 5);
A(:) = sort(A(:);
Will preserve colum-major-ordering, or as you said you prefer row-major ordering:
A = rand(3, 5);
A = reshape(sort(A(:)), fliplr(size(A)).';
Note the fliplr to store columnwise with reversed dimension and then the .' operator to transpose again the result.
EDIT
Even if matlab uses column-major-ordering for storing elements in memory, here below are two generic routines to work with row-major-order whatever the number of dimension of your array (i.e. no limited to 2D):
function [vector] = VectorizeWithRowMajorOrdering(array)
%[
axisCount = length(size(array)); % Squeezed size of original array
permutation = fliplr(1:(axisCount + 2)); % +2 ==> Trick to vectorize data in correct order
vector = permute(array, permutation);
vector = vector(:);
%]
end
function [array] = ReshapeFromRowMajorOrdering(vector, siz)
%[
siz = [siz( : ).' 1]; % Fix size if only one dim
array = NaN(siz); % Init
axisCount = length(size(array)); % Squeezed size!
permutation = fliplr(1:(axisCount + 2)); % +2 ==> Trick to vectorize data in correct order
array = reshape(vector, [1 1 fliplr(size(array))]);
array = ipermute(array, permutation);
%]
end
This can be useful when working with data coming from C/C++ (these languages use row-major-ordering). In your case this can be used this way:
A = rand(3, 5);
A = ReshapeFromRowMajorOrdering(sort(A(:)), size(A));

Extract elements of matrix from a starting element and size

Sorry about the bad title, I'm struggling to word this question well. Basically what I want to do is extract elements from a 2d matrix, from row by row, taking out a number of elements (N) starting at a particular column (k). In for loops, this would look like.
A = magic(6);
k = [2,2,3,3,4,4]; % for example
N = 3;
for j = 1:length(A)
B(j,:) = A(j,k(j):k(j)+N-1);
end
I figure there must be a neater way to do it than that.
You could use bsxfun to create an array of indices to use. Then combine this with the row numbers and pass it to sub2ind.
inds = sub2ind(size(A), repmat(1:size(A, 1), 3, 1), bsxfun(#plus, k, (0:(N-1))')).';
B = A(inds);
Or alternately without sub2ind (but slightly more cryptic).
B = A(bsxfun(#plus, 1:size(A,1), ((bsxfun(#plus, k, (0:(N-1)).')-1) * size(A,1))).');
Here's one approach using bsxfun's masking capability and thus logical indexing -
C = (1:size(A,2))';
At = A.';
B = reshape(At(bsxfun(#ge,C,k) & bsxfun(#lt,C,k+N)),N,[]).';

Treat each row of a matrix as a vector in a MATLAB function

Say I have a nxm matrix and want to treat each row as vectors in a function. So, if I have a function that adds vectors, finds the Cartesian product of vectors or for some reason takes the input of several vectors, I want that function to treat each row in a matrix as a vector.
This sounds like a very operation in Matlab. You can access the ith row of a matrix A using A(i, :). For example, to add rows i and j, you would do A(i, :) + A(j, :).
Given an nxm matrix A:
If you want to edit a single column/row you could use the following syntax: A(:, i) for the ith-column and A(i, :) for ith-row.
If you want to edit from a column/row i to a column/row j, you could use that syntax: A(:, i:j) or A(i:j, :)
If you want to edit (i.e.) from the penultimate column/row to the last one, you could you: A(:, end-1:end) or A(end-1:end, :)
EDIT:
I can't add a comment above because I don't have 50 points, but you should post the function setprod. I think you should be able to do what you want to do, by iterating the matrix you're passing as an argument, with a for-next statement.
I think you're going to have to loop:
Input
M = [1 2;
3 4;
5 6];
Step 1: Generate a list of all possible row pairs (row index numbers)
n = size(M,1);
row_ind = nchoosek(1:n,2)
Step 2: Loop through these indices and generate the product set:
S{n,n} = []; //% Preallocation of cell matrix
for pair = 1:size(row_ind,1)
p1 = row_ind(pair,1);
p2 = row_ind(pair,2);
S{p1,p2} = setprod(M(p1,:), M(p2,:))
end
Transform the matrix into a list of row vectors using these two steps:
Convert the matrix into a cell array of the matrix rows, using mat2cell.
Generate a comma-separated list from the cell array, using linear indexing of the cell contents.
Example: let
v1 = [1 2];
v2 = [10 20];
v3 = [11 12];
M = [v1; v2; v3];
and let fun be a function that accepts an arbitrary number of vectors as its input. Then
C = mat2cell(M, ones(1,size(M,1)));
result = fun(C{:});
is the same as result = fun(v1, v2, v3).

matlab parse file into cell array

I have a file in the following format in matlab:
user_id_a: (item_1,rating),(item_2,rating),...(item_n,rating)
user_id_b: (item_25,rating),(item_50,rating),...(item_x,rating)
....
....
so each line has values separated by a colon where the value to the left of the colon is a number representing user_id and the values to the right are tuples of item_ids (also numbers) and rating (numbers not floats).
I would like to read this data into a matlab cell array or better yet ultimately convert it into a sparse matrix wherein the user_id represents the row index, and the item_id represents the column index and store the corresponding rating in that array index. (This would work as I know a-priori the number of users and items in my universe so ids cannot be greater than that ).
Any help would be appreciated.
I have thus far tried the textscan function as follows:
c = textscan(f,'%d %s','delimiter',':') %this creates two cells one with all the user_ids
%and another with all the remaining string values.
Now if I try to do something like str2mat(c{2}), it works but it stores the '(' and ')' characters also in the matrix. I would like to store a sparse matrix in the fashion that I described above.
I am fairly new to matlab and would appreciate any help regarding this matter.
f = fopen('data.txt','rt'); %// data file. Open as text ('t')
str = textscan(f,'%s'); %// gives a cell which contains a cell array of strings
str = str{1}; %// cell array of strings
r = str(1:2:end);
r = cellfun(#(s) str2num(s(1:end-1)), r); %// rows; numeric vector
pairs = str(2:2:end);
pairs = regexprep(pairs,'[(,)]',' ');
pairs = cellfun(#(s) str2num(s(1:end-1)), pairs, 'uni', 0);
%// pairs; cell array of numeric vectors
cols = cellfun(#(x) x(1:2:end), pairs, 'uni', 0);
%// columns; cell array of numeric vectors
vals = cellfun(#(x) x(2:2:end), pairs, 'uni', 0);
%// values; cell array of numeric vectors
rows = arrayfun(#(n) repmat(r(n),1,numel(cols{n})), 1:numel(r), 'uni', 0);
%// rows repeated to match cols; cell array of numeric vectors
matrix = sparse([rows{:}], [cols{:}], [vals{:}]);
%// concat rows, cols and vals into vectors and use as inputs to sparse
For the example file
1: (1,3),(2,4),(3,5)
10: (1,1),(2,2)
this gives the following sparse matrix:
matrix =
(1,1) 3
(10,1) 1
(1,2) 4
(10,2) 2
(1,3) 5
I think newer versions of Matlab have a stringsplit function that makes this approach overkill, but the following works, if not quickly. It splits the file into userid's and "other stuff" as you show, initializes a large empty matrix, and then iterates through the other stuff, breaking it apart and placing in the correct place in the matrix.
(I Didn't see the previous answer when I opened this for some reason - it is more sophisticated than this one, though this may be a little easier to follow at the expense of slowness). I throw in the \s* into the regex in case the spacing is inconsistent, but otherwise don't perform much in the way of data-sanity-checking. Output is the full array, that you can then turn into a sparse array if desired.
% matlab_test.txt:
% 101: (1,42),(2,65),(5,0)
% 102: (25,78),(50,12),(6,143),(2,123)
% 103: (23,6),(56,3)
clear all;
fclose('all');
% your path will vary, of course
file = '<path>/matlab_test.txt';
f = fopen(file);
c = textscan(f,'%d %s','delimiter',':');
celldisp(c)
uids = c{1}
tuples = c{2}
% These are stated as known
num_users = 3;
num_items = 40;
desired_array = zeros(num_users, num_items);
expression = '\((\d+)\s*,\s*(\d+)\)'
% Assuming length(tuples) == num_users for simplicity
for k = 1:num_users
uid = uids(k)
tokens = regexp(tuples{k}, expression, 'tokens');
for l = 1:length(tokens)
item_id = str2num(tokens{l}{1})
rating = str2num(tokens{l}{2})
desired_array(uid, item_id) = rating;
end
end

Populate vectors using for loop

I have a solution to creating a vector for just one element of a matrix:
[dx,dy] = gradient(Im);
orient11 = [(-dx(1,1)) (dy(1,1)) 0];
where
size(orient11) =
0 0 0
ie for the first element of orient, namely orient11, is a vector. How do I do this for all the other elements, so I have orient12, orient13....orientnn. I know I need a for loop, however what object do I store the vectors into from the for loop? I have discovered I can't create a matrix of vectors.
Thanks in advance.
You can try building an N-by-N-by-3 matrix, but it won't be so convenient to manipulate. This is because extracting a vector from this matrix would yield a 1-by-1-by-3 vector, which you would need to reshape. Definitely not fun.
Instead, I suggest that you build an N-by-N cell array of 1-by-3 vectors, like so:
[dx, dy] = gradient(Im);
vec = #(i)[-dx(i), dy(i), 0];
orient = arrayfun(vec, reshape(1:numel(dx), size(dx)), 'UniformOutput', 0);
To access a vector, use the curly braces. For example, the vector at the (1, 2) position would be:
orient12 = orient{1, 2};
Hope it helps!
v = -2:0.2:2;
[x,y] = meshgrid(v);
z = x .* exp(-x.^2 - y.^2);
[px,py] = gradient(z,.2,.2);
orient11 = [(-px(1,1)) (py(1,1)) 0]; % based off of your concatination there.
size(orient11)
I then get:
ans =
1 3
If you're looking to just grab the first column of data from the gradients you have and want to just stack zeros with them, you can do this:
orient11 = [(-px(:,1)) (py(:,1)) zeros(size(px,1),1)];
Instead of a for loop.
Update:
Orient = zeros(size(px,1),3,size(px,2));
for n = 1:size(px,1)
Orient(:,:,n) = [(-px(:,n)) (py(:,n)) zeros(size(px,1),1)];
end
The layout of Orient is now your -px, py, 0 in layers. Each layer represents the column from the initial data. So if you wanted to get access to row 4 column 14, you would call Orient(4,:,14).
Hope that makes sense and helps!