how to sum a structure array in matlab? - matlab

I have a structure array in which every value is a number, I would like to do the sum of these structures.
Example:
S is structure array and every element has the same structure
S(1).a = 1
S(1).b.c = 1
S(1).b.d = 2
S(2).a = 2
S(2).b.c = 3
S(2).b.d = 4
sum(S) should be a structure 'SUM' with fields :
SUM.a = 1+2 = 3
SUM.b.c = 1+3 = 4
SUM.b.d = 2+4 = 6

I did not find any matlab function to achieve this, so I programmed this function:
function out = sumStruct(in)
% sum structure field per field
if isstruct(in)
for f = fields(in)'
out.(f{:}) = sumStruct([in.(f{:})]);
end
else
out = sum(in);
end
end
If I do SUM = sumStruct(S), I get what I want.

Related

Matlab Structures: How can I pass a concatenated string as a field to a structure?

I have a structure XYZ containing information about elements 01 to 04. I need to read or write to the elements based on the provided instance ("inst") number. Matlab gives the error "Reference to non-existent field 'strcat' or 'element'. I understand why Matlab gives the error. Trying to figure out how could I pass the field information to read or write to the element?
% XYZ structure
XYZ.Element_1 = 1;
XYZ.Element_2 = 5;
XYZ.Element_3 = 6;
XYZ.Element_4 = 7;
%Instance number
inst='1'
%Concatenate instance information to obtain the field
element=strcat('Element_', inst);
%Read the value of Element_1
var1=XYZ.strcat('Element_', inst);
var2=XYZ.element;
To access different fields you can use () around the Field_Name to be accessed.
% XYZ structure
XYZ.Element_1 = 1;
XYZ.Element_2 = 5;
XYZ.Element_3 = 6;
XYZ.Element_4 = 7;
inst = '1';
Field_Name = strcat('Element_', inst);
var1 = XYZ.(Field_Name);
inst = '2';
Field_Name = strcat('Element_', inst);
var2 = XYZ.(Field_Name);

Iterating structures array field's values

How to iterate/loop structures array field's values.
For 1x1 struct
student = struct();
student.name = 'jim';
student.gpa = 1.9;
I do this :
fields = fieldnames(student)
for i=1:numel(fields)
var = fields(i)
end
But I don't how to iterate 1 x 2 :
student = struct();
student(1).name = 'jim';
student(1).gpa = 1.9;
student(2).name = 'ryan';
student(2).gpa = 1.5;
You need to have either another for loop
fields = fieldnames(student);
for k = 1:numel(student)
for m = 1:numel(fields)
var = student(k).(fields{m});
end
end
Alternately, you can use the fact that the dot notation will create a comma separated list and you can place them in either a cell array (for strings) or a normal array
names = {student.name};
gpas = [student.name];
I typically prefer to use the second approach most often for accessing the same field from a struct array.

flatten a struct of arbitrarily nested arrays of integers into a flat array of integers

Is it possible to flatten an array of arbitrarily nested arrays of integers into a flat array of integers in Matlab? For example,
[[1,2,[3]],4] -> [1,2,3,4]
Any kind of guidance will be helpful. Thanks.
For example,
a.c = [5,4];
a.b.a=[9];
a.b.d=[1,2];
a= b: [1x1 struct]
c: [5 4]
In this case, my output will be
output= [9,1,2,5,4]
I think you will have to adapt the flatten function from the file exchange to use struct2cell so something like this:
function C = flatten_struct(A)
A = struct2cell(A);
C = [];
for i=1:numel(A)
if(isstruct(A{i}))
C = [C,flatten_struct(A{i})];
else
C = [C,A{i}];
end
end
end
This results in:
a.c = [5,4];
a.b.a=[9];
a.b.d=[1,2];
flatten_struct(a)
ans =
5 4 9 1 2
So the order is in the order you declared your struct instead of in your example order which I presume is alphabetical. But you have control over this so it shouldn't be a problem.
I have a preliminary hack which does work but rather clumsily. It descends recursively, saving structure names and unpacking the returned structure at each "level" .
% struct2sims converter
function simout = struct2sims(structin)
fnam = fieldnames(structin);
for jf = 1:numel(fnam)
subnam = [inputname(1),'_',fnam{jf}];
if isstruct(structin.(fnam{jf}) ) ,
% need to dive; build a new variable that's not a substruct
eval(sprintf('%s = structin.(fnam{jf});', fnam{jf}));
eval(sprintf('simtmp = struct2sims(%s);',fnam{jf}) );
% try removing the struct before getting any farther...
simout.(subnam) = simtmp;
else
% at bottom, ok
simout.(subnam) = structin.(fnam{jf});
end
end
% need to unpack structs here, after each level of recursion
% returns...
subfnam = fieldnames(simout);
for kf = 1:numel(subfnam)
if isstruct(simout.(subfnam{kf}) ),
subsubnam = fieldnames(simout.(subfnam{kf}));
for fk = 1:numel(subsubnam)
simout.([inputname(1),'_',subsubnam{fk}])...
= simout.(subfnam{kf}).(subsubnam{fk}) ;
end
simout = rmfield(simout,subfnam{kf});
end
end
% if desired write to file with:
% save('flattened','-struct','simout');
end

improve performance of a double for loop in matlab

I'm doing some analysis where I'm analysing hundreds of data files, which are being analysed iteratively. Here is an examples of the sort of data that I have:
start_time = datenum('1990-01-01');
end_time = datenum('2009-12-31');
time = start_time:end_time;
datx = rand(length(time),1);
daty = datx-2;
where I have a time variable and two data variables.
After loading the data I then need to pass the data through a function. However, I need to do this by including firstly the data from year 1 only, then from years 1 to 2; 1 to 3, 1 to 4 and so on until I pass the data through the function for the entire series. This can be performed with a loop with the following:
% split into different years
datev = datevec(time);
iyear = datev(:,1);
unique_year = unique(iyear);
for k = 1:length(unique_year);
idx = find(iyear >= unique_year(1) & iyear <= unique_year(k));
% select data for year
d_time = time(idx);
d_datx = datx(idx);
d_daty = daty(idx);
% now select individual years from this subset
datev2 = datevec(d_time);
iyear2 = datev2(:,1);
unique_year2 = unique(iyear2);
for k2 = 1:length(unique_year2);
idx2 = find(iyear2 == unique_year2(k2));
% select data for year
d_time2 = d_time(idx2);
d_datx2 = d_datx(idx2);
d_daty2 = d_daty(idx2);
% pass through some function
mae_out = some_function(d_datx2, d_daty2);
mae(k2) = mae_out;
end
mean_mae(k) = mean(mae);
end
function mae = some_function(datx, daty)
mae = mean(abs(datx - daty));
end
Note here that I'm using a very simple function as an example, and the actual function is more complex.
Having two loops like this takes a long time to run on my actual data. Is there a better/faster way that I can perform the above, possibly without loops?
If you record the previous result, you do not need the inner loop. You are currently computing a total of (20+21)/2 = 210 iterations, but you only need to compute 20. The key here is that mean(a(1:k)) == (mean(a(1:k-1))*(k-1) + a(k)) / k (by the definition of mean). Another optimization is to use logical indexing instead of find. It takes up a bit more space, but is much faster.
% split into different years
datev = datevec(time);
iyear = datev(:,1);
unique_year = unique(iyear);
for k = 1:length(unique_year);
idx = (iyear == unique_year(k));
% select data for year
d_time = time(idx);
d_datx = datx(idx);
d_daty = daty(idx);
mae_out = some_function(d_datx, d_daty);
if k == 1
mean_mae(k) = mean_out;
else
mean_mae(k) = (mean_mae(k-1) * (k-1) + mean(mean_out)) / k;
end
end
function mae = some_function(datx, daty)
mae = mean(abs(datx - daty));
end
As you can see, this should give you approximately 20x or more speedup.

Writing cellfun output using Matlab code for a folded data set

I have data a data file like this:
0 -7.09381e-10 7.88112e-09
1 -3.365e-09 3.96397e-08
2 -1.74014e-09 1.3715e-08
3 -6.79327e-10 4.74787e-09
4 -1.92799e-10 1.56609e-09
5 6.53422e-11 5.09169e-10
6 5.21863e-11 1.73983e-10
7 5.64361e-11 6.29614e-11
0 -9.44027e-10 8.14559e-09
1 -2.02866e-09 4.29019e-08
2 -2.2109e-10 1.57419e-08
3 4.55366e-11 5.97503e-09
4 1.70868e-10 2.28134e-09
5 1.90134e-10 8.52557e-10
6 4.4223e-11 3.2142e-10
7 7.2096e-12 1.22047e-10
and another 100 sets of data in this sequence one after another. The first column indices are time index. I fold the data and then calculate the ratio of column 2 and 3 using the following matlab code:
data_jknife =dlmread('datafile.txt',' ');
metadata = data_jknife(:,1); % a bidimensional array data_jknife, and want to access all its elements on the first column
data1 = data_jknife(:,2);%accessing all the elements on the second clomun
data2 = data_jknife(:,3);
groupedMetaData = arrayfun(#(x) metadata(x:4:end), 1:4 ,'UniformOutput',false );
groupedData1 = arrayfun(#(x) data1(x:4:end), 1:4 ,'UniformOutput',false ); %grouping data from the second column
groupedData2 = arrayfun(#(x) data2(x:4:end), 1:4 ,'UniformOutput',false );
flippedData1 = fliplr(groupedData1);
flippedData1 = flippedData1(1:2);
foldedData1 = cellfun(#(x,y) mean([x y],2), flippedData1 ,groupedData1(1:numel(flippedData1)),'UniformOutput',false);
flippedData2 = fliplr(groupedData2);
flippedData2 = flippedData2(1:4);
foldedData2 = cellfun(#(x,y) mean([x y],2), flippedData2 ,groupedData2(1:numel(flippedData2)),'UniformOutput',false);
foldedData = cellfun(#rdivide, foldedData1, foldedData2,'UniformOutput',false);
So the output of the foldedData should be like this:
0 R(0)
1 R(1)
0 R'(0)
1 R'(1)
2 R'(2)
where R is 2nd column divided by 3rd column of the folded data for corresponding time slices. Now I would like to write the output in a file in the above format. But I don't know how to do that. Could anybody please help me with that? Thanks in advance. So here is the numerical values of the operation
ok. So the folding acts like this for the first sequence of the data set:
2nd column elements(I take average of t= 0,3,4,7 data)
((-7.09381*10^-10) + (-6.79327*10^-10) + (-1.92799*10^-10) + 
(5.64361*10^-11))/4 =
-3.81268*10^-10
3rd column elements:
((7.88112*10^-09) + (4.74787*10^-09) + (1.56609*10^-09) + (6.29614*10^-11))/4 =
3.56451*10^-9
then I take average of t= 1,2,5,7 data. So the 2nd column is:
((-3.365*10^-09) + (-1.74014*10^-09) + (6.53422*10^-11) + (5.64361*10^-11))/4=
-1.24584*10^-9
3rd column is :
((3.96397*10^-08) + (1.3715*10^-08) + (5.09169*10^-10) + (1.73983*10^-10))/4=
1.35095*10^-8
So for the first sequence of data the output is :
R0 = (-3.81267725`*^-10)/(3.5645103500000007`*^-9) = -0.106962
R1 = (-1.245840425`*^-9)/(1.35095*10^-8) = -0.0922198
therefore the desired output for the 1st sequence is :
0 -0.106962
1 -0.0922198
Code
%%// input_filepath and output_filepath are the paths to the input and
%%// output files
d1 = dlmread(input_filepath,' ')
t1 = permute(reshape(d1',24,[]),[1 3 2]) %%//'
d1 = permute(reshape(t1,3,8,[]),[2 1 3])
d2 = d1([0 3 4 7]+1,[2 3],:)
d22 = d1([1 2 5 7]+1,[2 3],:)
t1 = [mean(d2) ; mean(d22)]
t2 = t1(:,1,:)./t1(:,2,:)
out = [repmat([0:size(t1,1)-1]',size(t2,3),1) t2(:)] %%//'
datacell = cellstr(num2str(out))
fid1 = fopen(output_filepath,'w');
for k = 1:size(datacell,1)
fprintf(fid1,'%s\n',datacell{k,:});
end
fclose(fid1);