Select specific data from all fields in structure

15 次查看(过去 30 天)
Hi all,
I have a data structure (data_freq) where all the data of my subjects is stored:
data_freq =
p28: [1x1 struct]
p29: [1x1 struct]
p30: [1x1 struct]
One field contains the sessions 'OFF' and 'ON': data_freq.p28
ans =
OFF: [1x1 struct]
ON: [1x1 struct]
And within the field 'OFF' or 'ON' there are the different conditions: data_freq.p28.OFF
ans =
Rest1: [1x1 struct]
Rest2: [1x1 struct]
Rest3: [1x1 struct]
RestCOG1: [1x1 struct]
RestCOG2: [1x1 struct]
RestCOG3: [1x1 struct]
And within these condition fields there is a field containing a matrix which represents my data. My question: I would like to select all these matrices for a specific subset of subjects/sessions/conditions. For example, I would like to select the fields of all subjects, the session 'OFF' and condition 'Rest1'. I'm not really familiar with this type of selection (or if its even possible in a simple way). I usually store my data in a matrix where every columns encodes the session/condition, after which I select it in the following way: select = data_freq(:,2)==sessioncode & data_freq(:,3)==conditioncode; data_freq = data_freq(select,:);
Does anyone know if you can do something similar like this for structures? I know about dynamic fields so I can use that if I need one specific field, but how do I select multiple fields (i.e. for all subjects, or if I would want two conditions)? I guess it would have to look something like this:
mtx = data_freq.(all).('OFF').('Rest1').data mtx = {3x1 cell}
Where one cell contains an [MxN double] (so select the data for all three subjets with session OFF and condition Rest1). The reason I want this is that after this step I can easily calculate averages/std of my data.

回答(2 个)

Guillaume
Guillaume 2015-6-16
编辑:Guillaume 2015-6-16
Multilevel structure arrays are difficult to manipulate in matlab so I tend to avoid them. In your case it's not so much of a problem as all your structures are scalar.
An alternative to eval often suggested on this forum is to use structures with dynamic field names, as you've done for your p28 to p30 fields. I think it is just as bad a practice as eval as you still end up with unknown variable names. A structure array would make more sense, but see point 1.
Anyway with your example, the simplest way to access the data is to use structfun to iterate over the first level, and plain dot notation for the remaining. if the final level data is not scalar, then you have to specify 'UniformOutput', false to structfun. In that case, you can then convert the output structure into a cell array with struct2cell:
%build a demo / testing structure:
node0 = @() struct('data', rand(5, 3));
node1 = @() struct('Rest1', node0(), ...
'Rest2', node0(), ...
'Rest3', node0(), ...
'RestCOG1', node0(), ...
'RestCOG2', node0(), ...
'RestCOG3', node0());
node2 = @() struct('OFF', node1(), 'ON', node1());
node3 = @() struct('p28', node2(), 'p29', node2(), 'p30', node2());
data_freq = node3();
%access data_freq.(all).('OFF').('Rest1').data
mtx = struct2cell(structfun(@(px) px.OFF.Rest1.data, data_freq, 'UniformOutput', false))
  2 个评论
Michiel
Michiel 2015-6-16
Thank you for the extended answer!
I encountered this option as well and it works well in this particular case. However, the freedom to select data is still limited; let's say I want to select these fields in 2 out of 3 subjects, or if I want to select all the data from Rest1 and Rest2. I wouldn't be able to do this with this method I think?
Guillaume
Guillaume 2015-6-16
The following function will allow you to do this:
function out = selectfields(s, fields, scalarfields)
%selectfields return the fields of a structure array that matches the given list of field names
%
%syntax:
% out = selectfields(s, fields)
% out = selectfields(s, fields, asarray)
%
%with:
% s: a structure
% fields: a cell array of field names
% scalarfields: logical indicating whether the returned fields are scalar (true, default) or not
% out: a column vector of field values (if scalarfields is true or omitted), a column cell array otherwise
%
%author: G. de Sercey
validateattributes(s, {'struct'}, {});
validateattributes(fields, {'cell'}, {});
if nargin < 3
scalarfields = true;
else
validateattributes(scalarfields, {'logical'}, {'scalar'});
end
if isrow(fields)
fields = fields';
end
out = arrayfun(@(ss) cellfun(@(f) ss.(f), fields, 'UniformOutput', scalarfields), s, 'UniformOutput', false);
out = vertcat(out{:});
end
You can then select the output you want by chaining calls to selectfields:
selection = selectfields(data_freq, {'p28', 'p30'});
selection = selectfields(selection, {'ON'});
selection = selectfields(selection, {'Rest1', 'Rest2', 'RestCOG2'});
mtx = selectfields(selection, {'data'}, false)
Note that the last line could also be:
mtx = {selection.data}'

请先登录,再进行评论。


Peter Perkins
Peter Perkins 2015-6-16
编辑:Peter Perkins 2015-6-16
Michiel, this doesn't answer your question directly, but may be of some help if you have a version of MATLAB from R2013b on.
You don't say why your data are organized like that. There's nothing wrong with that format per se, but it makes the kind of subselection you want difficult. It may be the format expected by some other code, or some other external reason. But you might find it simpler to put the data into a table, in much the same way you have described as, "store my data in a matrix".
For example, first cook up some fake data as you describe:
subjects = {'p28' 'p29' 'p30'}';
sessions = {'ON' 'OFF'}';
conditions = {'Rest1' 'Rest2' 'Rest2' 'RestCOG1' 'RestCOG2' 'RestCOG2'}';
for i1 = 1:length(subjects)
subject = subjects{i1};
for i2 = 1:length(sessions)
session = sessions{i2};
for i3 = 1:length(conditions)
condition = conditions{i3};
data_freq.(subject).(session).(condition) = struct('a',randn,'b',randn);
end
end
end
Now convert to a table. There are probably many ways to do this, here's one:
nrows = length(subjects)*length(sessions)*length(conditions);
Subject = categorical( repelem(subjects,length(sessions)*length(conditions),1) );
Session = categorical( repmat(repelem(sessions,length(conditions),1),length(subjects),1) );
Condition = categorical( repmat(conditions,length(subjects)*length(sessions),1) );
A = NaN(nrows,1);
B = NaN(nrows,1);
df = table(Subject,Session,Condition,A,B);
j = 0;
for i1 = 1:length(subjects)
subject = subjects{i1};
for i2 = 1:length(sessions)
session = sessions{i2};
for i3 = 1:length(conditions)
condition = conditions{i3};
j = j + 1;
df(j,{'A' 'B'}) = struct2table(data_freq.(subject).(session).(condition));
end
end
end
OK, what to do with this? Here are three versions of "select the fields of all subjects, the session 'OFF' and condition 'Rest1'":
>> offRest1 = df.Session=='OFF' & df.Condition=='Rest1';
>> df(offRest1,:)
ans =
Subject Session Condition A B
_______ _______ _________ _______ ________
p28 OFF Rest1 -1.6258 -1.9648
p29 OFF Rest1 0.43638 -0.50436
p30 OFF Rest1 0.49016 0.76525
>> df{offRest1,{'A' 'B'}}
ans =
-1.6258 -1.9648
0.43638 -0.50436
0.49016 0.76525
>> df.A(offRest1)
ans =
-1.6258
0.43638
0.49016
  2 个评论
Michiel
Michiel 2015-6-16
Thanks Peter for this workaround. It indeed works well with solving this problem.
You are right that this way of structuring is not very convenient for this type of selection. I like - however - that you don't have to decode everything but you can just use your subject/session/condition strings as fieldnames. But the real reason I structured it like this is because of the (third party) toolbox I'm using to analyze my data, which returns its analysis in this type of structure. This full structure is usually needed if you want to perform other steps in the analysis pipeline, so that's why I did not convert the structure in an (easier to select) matrix.
Judging by both of your comments I think there is no standard way to solve my problem without doing some sort of conversion or giving in on some limitations for selecting data. I think instead I will write a function myself to still be able to select multiple fields within a multi-level structure, hopefully that will be.
Guillaume
Guillaume 2015-6-17
Hum! You must have missed my reply to your comment. The function to do the selection is there.

请先登录,再进行评论。

类别

Help CenterFile Exchange 中查找有关 Cell Arrays 的更多信息

Community Treasure Hunt

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

Start Hunting!

Translated by