Matlab: Series of Variables in a Loop - matlab

This is a solution from another stackoverflow participant who helped me out.
Data is coming from a csv file:
States Damage Blizzards
Indiana 1 3
Alabama 2 3
Ohio 3 2
Alabama 4 2
%// Parse CSV file
[States, Damage, Blizzards] = textread(csvfilename, '%s %d %d', ...
'delimiter', ',', 'headerlines', 1);
%// Parse data and store in an array of structs
[U, ix, iu] = unique(States); %// Find unique state names
S = struct('state', U); %// Create a struct for each state
for k = 1:numel(U)
idx = (iu == k); %// Indices of rows matching current state
S(k).damage = Damage(idx); %// Add damage information
S(k).blizzards = Blizzards(idx); %// Add blizards information
end
In MATLAB, I need to create a series of assigned variables (A1,A2,A3) in a loop. So I have structure S with 3 fields: state, tornado, hurricane.
Now I have attempted this method to assign A1 =, A2 =, which I got an error because it will not work for structures:
for n = 1:numel(S)
eval(sprintf('A%d = [1:n]',S(n).states));
end
Output goal is a series of assigned variables in the loop to the fields of the structure:
A1 = 2 3
A2 = 2 3
A3 = 4 5

I'm not 100% sure I understand your question.
But maybe you are looking for something like this:
for n = 1:numel(S)
eval(sprintf('A%d = [S(n).damage S(n).blizzards]',n));
end
BTW using evalc instead of eval will suppress the command line output.
A little explanation, why
eval(sprintf('A%d = [1:n]',S(n).state));
does not work:
S(1).state
returns
ans =
Alabama
which is a string. However,
A%d
expects a number (see this for number formatting).
Additionally,
numel(S)
yields
ans =
3
Therefore,
eval(sprintf('A%d = [1:n]',n));
will simply return the following output:
A1 =
1
A2 =
1 2
A3 =
1 2 3
Hence, you want n as a counter for the variable name, but compose the vector of the entries in the other struct-fields (damage and blizzards), again, using n as a counter.

Related

Finding the max number of a column in MATLAB, which allocates to a specific string

Consider that I have a table of such type in MATLAB:
Location String Number
1 a 26
1 b 361
2 c 28
2 a 45
3 a 78
4 b 82
I would like to create a script which returns only 3 rows, which would include the largest Number for each string. So in this case the table returned would be the following:
Location String Number
3 a 78
1 b 361
2 c 28
The actual table that I want to tackle is much greater, though I wrote this like that for simplicity. Any ideas on how this task can be tackled? Thank you in advance for your time!
You could use splitapply, with an id for each row.
Please see the comments for details...
% Assign unique ID to each row
tbl.id = (1:size(tbl,1))';
% Get groups of the different strings
g = findgroups(tbl.String);
% create function which gets id of max within each group
% f must take arguments corresponding to each splitapply table column
f = #(num,id) id(find(num == max(num), 1));
% Use splitapply to apply the function f to all different groups
idx = splitapply( f, tbl(:,{'Number','id'}), g );
% Collect rows
outTbl = tbl(idx, {'Location', 'String', 'Number'});
>> outTbl =
Location String Number
3 'a' 78
1 'b' 361
2 'c' 28
Or just a simple loop. This loop is only over the unique values of String so should be pretty quick.
u = unique(tbl.String);
c = cell(numel(u), size(tbl,2));
for ii = 1:numel(u)
temp = tbl(strcmp(tbl.String, u{ii}),:);
[~, idx] = max(temp.Number);
c(ii,:) = table2cell(temp(idx,:));
end
outTbl = cell2table(c, 'VariableNames', tbl.Properties.VariableNames);
Finding max values of each string my idea is.
Create a vector of all your strings and include them only one time. Something like:
strs=['a','b','c'];
Then create a vector that will store maximum value of each string:
n=length(strs);
max_values=zeros(1,n);
Now create a loop with the size of the whole data to compare current max_value with the current value and substitute if current_value>max_value:
for i=1:your_table_size
m=find(strs==current_table_string); % This finds the index of max_values
if max_values(m)<current_table_Number % This the the i_th row table_number
max_values(m)=current_table_Number;
end
end

Extract Matrix columns and store them in individual vectors

I have a A matrix of size MxN where M is large and N is around 30.
[A,B,C,...,AD] = A(:,1:30)
The reason I am asking that is that I would like to give the columns a specific name (here A,B a,c,...,AD) and not being force to write:
[A,B,C,...,AD] = deal(A(:,1),A(:,2),A(:,3),...,A(:,30))
It's usually better to keep all columns together in the matrix and just access them through their column index.
Anyway, if you really need to separate them into variables, you can convert the matrix to a cell array of its columns with num2cell, and then generate a comma-separated list to be used in the right-hand side of the assignment. Note also that in recent Matlab versions you can remove deal:
A = magic(3); % example matrix
Ac = num2cell(A, 1);
[c1 c2 c3] = Ac{:}; % or [c1 c2 c3] = deal(Ac{:});
For generating that lexicographical sequence I recently, out of ignorance, wrote this
Data = rand(2,671);
r = rem(size(Data,2),26);
m = floor(size(Data,2)/26);
Alf = char('A'+(0:25)'); %TeX-like char seq
if m == 0
zzz = Alf(1:r);
else
zzz = Alf;
for x = 1:m-1
zzz = char(zzz,[char(Alf(x)*ones(26,1)),Alf]);
end
if r > 0
zzz = char(zzz, [char(Alf(m+1)*ones(r,1)),Alf(1:r)] );
end
end
Depending on the number of columns it generates column names until ZZ. Please let me know if there is a readily made command for this in matlab.
You would never ever use eval for such things!!! eval use is dangerous and wrong (but you can't resist):
% ==========
% Assign Data to indices
% ==========
for ind = 1:size(Data,2)
eval([zzz(ind,:) '= Data(:,' num2str(ind) ');']);
end
and your workspace looks like an alphabet soup.

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).

Find number of consecutive elements before value changes (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

Loading a data efficiently in matlab

I have the data of the following form in a text file
Userid Gameid Count
Jason 1 2
Jason 2 10
Jason 4 20
Mark 1 2
Mark 2 10
................
.................
There are a total of 81 Gameids and I have around 2 million distinct users.
What I want is to read this text file and create a sparse matrix of the form
Column 1 2 3 4 5 6 .
Row1 Jason 2 10 20
Row2 Mark 2 10
Now I can load this text file in matlab and read the users one by one, reading their count and initializing the sparse array. I have tried this, it takes 1 second to initialize the row of one user. So for a total of 2 million users, it will take me a lot of time.
what would be the most efficient way to do this?
Here is my code
data = sparse(10000000, num_games);
loc = 1;
for f=1:length(files)
file = files(f).name;
fid = fopen(file,'r');
s = textscan(fid,'%s%d%d');
count = (s(:,2));
count = count{1};
position = (s(:,3));
position = position{1};
A=s{:,1};
A=cellstr(A);
users = unique(A);
for aa = 1:length(Users)
a = strfind(A, char(Users(aa)));
ix=cellfun('isempty',a);
index = find(ix==0);
data(loc,position(index,:)) = count(index,:);
loc = loc + 1;
end
end
Avoid the inner loop by usingunique once more for GameID.
Store the user names, because in your original code you can't tell which name - relates to each row. The same thing for game IDs.
Make sure to close the file after opening it.
sparse matrix does not support 'int32' you need to store your data as double.
% Place holders for Count
Rows = [];
Cols = [];
for f = 1:length(files)
% Read the data into 's'
fid = fopen(files(f).name,'r');
s = textscan(fid,'%s%f%f');
fclose(fid);
% Spread the data
[U, G, Count{f}] = s{:};
[Users{f},~, r] = unique(U); % Unique user names
[GameIDs{f},~,c] = unique(G); % Unique GameIDs
Rows = [Rows; r + max([Rows; 0])];
Cols = [Cols; c + max([Cols; 0])];
end
% Convert to linear vectors
Count = cell2mat(Count');
Users = reshape([Users{:}], [], 1);
GameIDs = cell2mat(GameIDs');
% Create the sparse matrix
Data = sparse(Rows, Cols, Count, length(Users), length(GameIDs), length(Count));
The Users will contain be the Row header (user names) and GameIDs the Column header.