Manipulating a structure for a more flexible plot

Hi,
I am building an app to fit different models to experimental data, each corresponding to a given values of the variable Q, and at the end to plot the results for each parameters vs Q.
The fit output is a structure, named Int, with many fields.
For simplicity I consider here the case with only 3 Qs values:
Int =
struct with fields:
Q: [7 9 10]
ParsNames: {{13×1 cell} {13×1 cell} {13×1 cell}}
ParsFit: {[13×1 double] [13×1 double] [13×1 double]}
eParsFit: {[13×1 double] [13×1 double] [13×1 double]}
In this case the model has 13 parameters, that are stored for each Q in the cells ParsFit, with their errors, and names.
The ParsNames are always the same (Int.ParsNames{1}=Int.ParsNames{2}= ...), in this case:
Int.ParsNames{1} =
{'A0' }
{'A1' }
{'B0' }
{'B1' }
{'IL' }
{'GL' }
{'ID1' }
{'GD1' }
{'ED1' }
{'ID2' }
{'GD2' }
{'ED2' }
{'Chi2_r'}
I need to plot all the variables vs Q. For instance, for the 3 values of the parameter at the position 12:
for k = 1 : length(Int.Q)
ED2(k)=Int.ParsFit{1,k}(12,1); % I used the same variable name as the ParsNames
eED2(k)=Int.eParsFit{1,k}(12,1);
end
figure; errorbar(Int.Q,ED2,eED2)
This of course works, but I'd like to make the app more flexible because the number and name of parameters can vary.
In the example above, I named myself the variable with the proper name (ED2), and I’d avoid that: using another model, the parameter ED2 could be missing, and I would like to avoid writing different scripts for each case
As far as I understood, it is not suggested to define all the variables using dynamically the values contained in ParNames.
I wonder whether (and how) I can build another structure that is almost the transpose of Int with fields equal to those of Int:
newInt.A0 with all the Int.ParsFit{1,k}(1,1)
newInt.A1 with all Int.ParsFit{1,k}(2,1)
........
and put inside them all the Int.ParsFit{1,k}(12,1), and then plot all of them.
Is it possible to name the fields of the new structure with those of the original one Int.ParsNames{1} ?
Do you have suggestion to plot the data in the more clean, logical, and flexible way?
Thanks for your help!

2 Comments

"Is it possible to name the fields of the new structure with those of the original one Int.ParsNames{1} ?"
Of course: you can get a cell array of all fieldnames using FIELDNAMES, and create fields is a structure either using SETFIELD or dynamic fieldnames (simpler):
In practice this will require a loop and paying careful attention to the details, but it isn't difficult.
"Do you have suggestion to plot the data in the more clean, logical, and flexible way?"
I would suggest that rather than a scalar structure (where every field has size 1xN) you might like to consider using a 1xN non-scalar structure, which means you don't need those cell arrays any more.
"I need to plot all the variables vs Q.... but I'd like to make the app more flexible because the number and name of parameters can vary."
Of course they can. There is absolutely no need for the variable you use in the code to have the same name as your meta-data. If you used a non-scalar structure then you could trivially concatenate them into matrices too (rather than lots of anti-pattern separate variables with ugly-numbered variable names), which would make plotting easier because you could use the inbuilt ability to plot matrices rather than trying to mess around with lots of separate variables and whatnot.
how to plot struct

Sign in to comment.

 Accepted Answer

Data in a 3x1 structure array (rather than a scalar structure with nested cell arrays as you showed) means slightly simpler data access because you can use a simpler syntax for accessing the field data in comma-separated lists.
C = {'A0';'A1';'B0';'B1';'IL';'GL';'ID1';'GD1';'ED1';'ID2';'GD2';'ED2';'Chi2_r'};
S(1).Q = 7;
S(1).ParsNames = C;
S(1).ParsFit = rand(13,1);
S(1).eParsFit = rand(13,1);
S(2).Q = 9;
S(2).ParsNames = C;
S(2).ParsFit = rand(13,1);
S(2).eParsFit = rand(13,1);
S(3).Q = 10;
S(3).ParsNames = C;
S(3).ParsFit = rand(13,1);
S(3).eParsFit = rand(13,1);
Plot:
Q = [S.Q];
M = [S.ParsFit];
ax = axes();
ax.LineStyleOrder = {'-o','-+','-*'};
hold(ax,'on')
plot(ax,Q,M.')
legend(S(1).ParsNames)

6 Comments

Thank you Stephen,
your suggestions made me advance a lot in my (still long) way to build my app.
My question concerned indeed only a small part of it.
Your suggestion helped me a lot even though I was not able to find the optimal solution for my problem.
The firts reason (I probably was not clear) is that I needed to make independent plots of the parameter (they have very different values, and also physical meaning, and an unique plot is ununderstandable).
The second reason is that I needed an errorbar plot, and I was not able to use the command you suggest.
What I did is:
To create an arrayt structure as you suggest with the good ParsNames and parameters with errors:
Q = Int.Q;
C=Int.ParsNames{1};
lC=length(C);
lQ=length(Q);
for k = 1 : lQ
Stest(k).ParsNames=C;
Stest(k).Q=Int.Q(k);
Stest(k).ParsFit=Int.ParsFit{k};
Stest(k).eParsFit=Int.eParsFit{k};
end
Mtest = [Stest.ParsFit];
eMtest = [Stest.eParsFit];
The plot below works but it gives 13 independent plots
for j=1:lC
figure(10+j); hold on
errorbar(Q,Mtest(j,:),eMtest(j,:))
legend(Stest(1).ParsNames(j))
end
Note that I was not able to use your nice suggestion with errorbar: am I doing some stupid error? I did not fond a solution in the help.
ax = axes;
ax.LineStyleOrder = {'-o','-+','-*'};
hold(ax,'on')
errorbar(ax,Q,Mtest.',eMtest.')
Error using errorbar (line 105)
X-data must be the same size as Y-data.
A step beyond for me is however not to have independent plots, but plots regrouped according to the type of parameters, that is according to their first letter: A, B, I, G, E, Ch.
This (probably not very efficient) script works and gives 6 grouped plots
indA = find(contains(C,'A'));
indB = find(contains(C,'B'));
indI = find(contains(C,'I'));
indG = find(contains(C,'G'));
indE = find(contains(C,'E'));
indC = find(contains(C,'Ch'));
for j=1:lC
% par A
figure(1);hold on
if ismember(j,indA)
h=errorbar(Q,Mtest(j,:),eMtest(j,:),'o')
h.DisplayName=string(Stest(1).ParsNames(j));
end
legend show
hold off
% par B
figure(2);hold on
if ismember(j,indB)
same as above
% par I
figure(3);hold on
if ismember(j,indI)
same as above
% par G
figure(4);hold on
if ismember(j,indG)
same as above
% par E
figure(5);hold on
if ismember(j,indE)
same as above
% par C
figure(6);hold on
if ismember(j,indC)
same as above
end
There are surely alternative loop that I could do, for instance defining the number of figures I need: for j=1:num_plot=numel(who('ind*'));
Ideally I'd like to gain in flexibility removing all the references by hand to the figure numbers and to the different variables.
This code is to be called in an app, and I am looking for the more efficient way to do it.
Is it possible to use the structure flexibility also in this case?
If you have any kind of suggestion ......
Thanks again
ps: I guess that your suggestion : S(1).eParsFit = randi(13,1);
should be changed in S(1).eParsFit = rand(13,1);What I did is
@Ferdi: thank you for the note about RANDI, I fixed it in my answer.
"What I did is To create an arrayt structure as you suggest with the good ParsNames and parameters with errors"
I was not really suggesting that you convert from one structure to another, but that you design your original data structure in a way that suits your data processing better.
"I was not able to use your nice suggestion with errorbar"
Of course you will need to check the orientations of the data arrays that you concatenate togther to create Mtest and eMtest, and finally check that the orientation of the data in those matrices is as one column for each desired line in the plot (assuming that you want simplify your code and plot multiple lines at once).
You cannot just copy code and hope that it works. You need to adapt it to your data (which I do not have).
"Ideally I'd like to gain in flexibility removing all the references by hand to the figure numbers and to the different variables."
As soon as you start writing lots of individual variable names like this:
indA = ..
indB = ..
indC = ..
...
then you have painted yourself into a corner and making it much more difficult for yourself. As soon as you copy-and-paste code like that (i.e. everywhere your wrote "same as above") then you are doing something wrong because computers are only really good at one thing: repeatedly performing simple operations many times in a loop. So when you sit and copy-and-paste code like that, you are just doing the computer's job for it.
This is MATLAB, so use arrays instead. For example, put the required groups into an array, not as lots of indivdual commands on lots of individual lines of code:
pfx = {'A','B', 'I', 'G', 'E', 'Ch'};
Well, that looks a lot better already. What is an easy thing we can tell the computer to do? Loop over an array. Something like this:
for k = 1:numel(pfx)
ind = find(startsWith(C,pfx{k}));
fgh = figure(k);
axh = axes(fgh);
... your plotting routine
end
It is not even clear if you need to set the LineStyleOrder: I used it to show all of the 13 lines in one axes. How many lines do you expect each axes to contain?
Stephen, you are very kind and clear.
Ok, I was already thinking that it would be worth to produce differently the orginal data: I will try and follow your advice. The orginal data are produced by another part of the app (the fitting routine) and with this code I callback only the fit outputs. Following your suggestion I will change the another part of the app.
I know that I was painting myself in the corner with indA and so on, but this was the only thing that I knew I could do.... I looked around but I did not find a command like startsWith.
About errorbar(ax,Q,Mtest.',eMtest.'): Mtest and eMtest have an identical structure and I naively thougth that I could simply extend the plot(ax,Q,Mtest.').
I unsuccessfully tried also with the same matrix, ie errorbar(ax,Q,Mtest.',Mtest.').
I will try to go through it tomorrow. I can of course send my data if it helps.
I expect no more than three/four lines for each plot.
Thanks again.
"About errorbar"
Always read the documentation! ERRORBAR has different inputs to PLOT, in particular it requires its 1st input (X) to have the same size as its 2nd (Y) input (unlike PLOT):
V = 1:5;
X = repmat(V(:),1,7);
Y = rand(5,7);
E = rand(5,7);
errorbar(X,Y,E)
Ferdi
Ferdi on 24 Mar 2022
Edited: Ferdi on 24 Mar 2022
Stephen, I wanted to let you know that I think to have solved my problem. I tried to make more flexible the choice of the pars name pfx = {'A','B', 'I', 'G', 'E', 'Ch'}; because their name can slightly vary in the various fits.
So, I am fine and you can stop to read the message here.
However, I append below the main sequence of what I am doing in case you like to have a look.
I will probably come back with other questions as I have still a long route in fornt of me.
Many thanks for your help.
%%%%%%%%%%
I have experimental data named I1D_L180_1A10, where the bolded strings may vary. The Q used in the following is the last number: Q=10 in this case.
My main app makes calls for some subset of data, for instance I1D_L180_1AQ, Q being an integer number (having a physical meaning).
I fix a given Q and make the fit. I store the data in the main app in a structure named app.results
%%
app.results =
struct with fields:
Q: 9
ParsNames: {13×1 cell}
ParsFit: [13×1 double]
eParsFit: [13×1 double]
Ifx: [100×1 double]
Ex: [100×1 double]
Ix: [100×1 double]
err_Ix: [100×1 double]
parRes: [1.3711 -0.0074 9.2646e-05]
Resx: [100×1 double]
Resd: [127×1 double]
T: 180
opt: [1×1 struct]
LB: [12×1 double]
UB: [12×1 double]
fix: [12×1 logical]
Ed: [127×1 double]
Ifd: [127×1 double]
YMnoC: [127×1 double]
YF_noC: [127×1 double]
YL: [127×1 double]
YD1: [127×1 double]
YD2: [127×1 double]
If I am happy with the fit I export the data, at a fixed Q, in a session .mat
Iall=app.results;
name = ['Res_' [app.SpectraName] '.mat'];
[fnm,pth] = uiputfile(name);
if ischar(fnm)
save(fullfile(pth,fnm),'Iall');
end
Now, we are at the core of my original question : I am writing another callback function where plotting (and making other stuff) with the outputs of the fit.
After your suggestions I ended up with the following :
data_folder = uigetdir(); %
if data_folder == 0
return;
end
% the good mat sessions
file_pth = fullfile(data_folder, 'Res*.mat');
filen = dir(file_pth);
% load data and get rid of the common field Iall
lQ=length(filen);
for k = 1 : lQ;
fname0 = filen(k).name;
fname = fullfile(filen(k).folder, fname0);
fprintf(1, 'Now reading %s\n', fname);
A=load(filen(k).name);
Int(k)=A.Iall;
end
% take out Qs, pars with errors
Q = [Int.Q]';
Mtest = [Int.ParsFit];
eMtest = [Int.eParsFit];
% regrouping pars according to their first letter
C=Int.ParsNames;
% where C'= {'A0'} {'A1'} {'B0'} {'B1'} {'IL'} {'GL'} {'ID1'} {'GD1'} {'ED1'} {'ID2'} {'GD2'} {'ED2'} {'Chi2_r'}
C1=extract(string(C),1);
[uC1,~,indC1] = unique(C1(:));
pfx = cellstr(uC1);
that gives the same result than pfx = {'A','B', 'I', 'G', 'E', 'Ch'};
% plot
for k = 1:numel(uC1)
ind = find(startsWith(C,pfx{k}));
fgh = figure(k);
axh = axes(fgh);
Qind=repmat(Q(:),1,numel(ind)); % thanks
errorbar(axh,Qind,Mtest(ind,:)',eMtest(ind,:)','o-');
xlim([0, max(Q)+1]);
legend(C(ind));
end
% some test data
Mtest =
0.0414 0.3010 0.0861
0.9754 0.9864 0.9892
0.0004 0.0005 0.0005
0.0000 0.0000 0.0000
0.0014 0.0001 0.0000
0.0700 0.0959 0.1527
0.0371 0.0206 0.0360
0.1048 0.1000 0.1271
5.5061 5.2542 4.4344
0.0424 0.0068 0.0330
0.7690 2.8483 1.2135
9.9690 11.0839 7.4763
3.8566 3.5473 3.3208
eMtest =
0.0097 0.0107 0.0167
0.0000 0.0000 0.0000
0.0000 0.0000 0.0000
0.0000 0.0000 0.0000
0.0003 0.0000 0.0000
0.0000 0.0000 0.0001
0.0087 0.0007 0.0070
0.0000 0.0000 0.0000
0.0000 0.0000 0.0000
0.0099 0.0002 0.0064
0.0000 0.0000 0.0000
0.0000 0.0000 0.0000
0 0 0
"However, I append below the main sequence of what I am doing in case you like to have a look."
Good work, it looks fine to me.
The main thing is, that you can see ways to use a loop to process your data groups.

Sign in to comment.

More Answers (0)

Categories

Products

Release

R2021b

Asked:

on 22 Mar 2022

Moved:

on 28 Mar 2024

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!