Gnarlywhale
Gnarlywhale

Reputation: 4240

Is it possible to average across array of structure sub-fields in Matlab without looping?

I have a function that processes an individual data set, and stores the outcome metrics in a data set like so:

trial_data.output_metric_a.value_1 = 2;
...
trial_1 = trial_data;

This function is applied to a number of different trials and are stored as an array struct:

% trial_1.output_metric_a.value_1 = 4
% trial_2.output_metric_a.value_1 = 2
trials = [trial_1 ; trial_2];

Is it possible to get averages and standard deviations of the sub-field values without looping through the data structure?

Ideally:

mean_trials = mean(trials)
mean_trials.output_metric_a.value_1 == 3 % true

A possible loop implementation to solve this could be (obviously this leaves much to be desired):

output_metric_a_value_1 = [];
...

for i:length(trials)
    output_metric_a_value_1(end+1) = trials(i).output_metric_a.value_1; 
    ... % For each output metric and value
end

mean_trials.output_metric_a.value_1 = mean(output_metric_a_value_1);

Upvotes: 3

Views: 450

Answers (3)

matheburg
matheburg

Reputation: 2180

Special Solution (not-nested struct > array)

As already mentioned, aiming for just one level of struct fields (not nested) one can basically go for a one-liner:

sarr_mean = cellfun(@(fn) mean([sarr.(fn)]), fieldnames(sarr))

Remark: In the not-nested case, there is not really a need to assign the resulting array back to a struct. If required, you can do it analogously to the full solution below.

Full Solution (nested struct > nested struct)

However, with arbitrarily nested arrays, I suggest to use a function such as the following:

% f... function handle
% s... nested struct array
function sarr_res = nestedSarrFun(f, s)
    if isstruct(s)
        % get fieldnames:
        fns = fieldnames(s);
        % get content:
        con = cellfun(@(fn) nestedSarrFun(f, [s.(fn)]), ...
                      fns, 'UniformOutput', false);
        % create return struct
        fnsCon = reshape([fns(:), con(:)]', [1,2*numel(fns)]);
        sarr_res = struct(fnsCon{:});
    else
        sarr_res = f(s);
    end
end

Usage Example

Define example struct array and apply mean via nestedSarrFun:

% define example struct array "sarr" with fields
%   .foo.bar1
%       .bar2
%   .dings
sarr = struct();
sarr(1).foo.bar1 = 2;
sarr(1).foo.bar2 = 7;
sarr(1).dings   = 1;
sarr(2).foo.bar1 = 5;
sarr(2).foo.bar2 = 5;
sarr(2).dings   = 2;

% apply mean to all nested fields:
sarr_mean = nestedSarrFun(@mean, sarr);

Example Result:

sarr_mean.foo.bar1 = 3.5
sarr_mean.foo.bar2 = 6
sarr_mean.dings = 1.5

Upvotes: 2

Amos Egel
Amos Egel

Reputation: 1196

In Matlab2017 (I am not sure about older versions), an array of structs can return an array of the field of its structs like so:

struct1.x = 1;
struct2.x = 2;

% array of 2 structs:
struct_array = [struct1, struct2];

% array of field x of each struct:
[struct_array.x]

which returns

ans =

     1     2

In your case, the data is not in a field of your struct, but in a subfield output_metric_a. Therefore, you first need to this twice:

trial1.output_metric_a.value_1 = 1;
trial2.output_metric_a.value_1 = 2;

trials = [trial1, trial2];

output_metric_a_array = [trials.output_metric_a];

value_1_array = [output_metric_a_array.value_1];

mean_of_value_1 = mean(value_1_array)

which returns

mean_of_value_1 =

    1.5000

Upvotes: 1

Adiel
Adiel

Reputation: 3071

You can convert the main struct to cell, then operate on the contents:

% your data base:
trial_1.output_metric_a.value_1 = 4
trial_2.output_metric_a.value_1 = 2
trials = [trial_1 ; trial_2];

% convert to cell:
Ctrials=struct2cell(trials);
Atrials=[Ctrials{:}];
meanTrials=mean([Atrials.value_1])
meanTrials=
     3

Upvotes: 4

Related Questions