Search code examples
matlabstructure

MATLAB Group Structure data by category


I have an array of structures where each struct contains 3 fields. The array is shown below in the image. The three fields are 'Measured_Signal_lvl', 'BiD', and 'SiD'. I want to bin the data by BiD and SiD. The BiD field can take on values from 0-7, and SiD can be [1,4,10,18] only. I want to put all the same BiDs in its respective group with their corresponding SiDs. So I expect to have a separate group for each BiD and there should be 8.

So far, what I have done is convert the array to a MATLAB table. Then I use the 'ismember' function to group all of the BiDs and store them in a separate table. Then, I repeat the same process, but on the already grouped BiD tables to get all of the SiDs within each BiD table. As you can see this approach is not too efficient and the code, as posted below is very repetitive. I would like to utilize a more efficient approach both in code and architecture.

Snapshot of the structure array:

Snapshot of the structure array

Code I currently have:

% Find all the BiD == 0 
bId_0 = st(ismember(st.BiD,0),:);

% Find all the BiD == 1 
bId_1 = st(ismember(st.BiD,1),:);

% Find all the BiD == 2 
bId_2 = st(ismember(st.BiD,2),:);

% Find all the BiD == 3 
bId_3 = st(ismember(st.BiD,3),:);

% Find all the BiD == 4 
bId_4 = st(ismember(st.BiD,4),:);

% Find all the BiD == 5 
bId_5 = st(ismember(st.BiD,5),:);

% Find all the BiD == 6 
bId_6 = st(ismember(st.BiD,6),:);

% Find all the BiD == 7 
bId_7 = st(ismember(st.BiD,7),:);

% % Drill Further to find the SIDs per BIDs
% % all BId 0s, SiD 1,4,10,18
bId_0_sId_1 = bId_0(ismember(bId_0.SiD,1),:);
bId_0_sId_4 = bId_0(ismember(bId_0.SiD,4),:);
bId_0_sId_10 = bId_0(ismember(bId_0.SiD,10),:);
bId_0_sId_18 = bId_0(ismember(bId_0.SiD,18),:);
% all BId 1s, SiD 1,4,10,18
bId_1_sId_1 = bId_1(ismember(bId_1.SiD,1),:);
bId_1_sId_4 = bId_1(ismember(bId_1.SiD,4),:);
bId_1_sId_10 = bId_1(ismember(bId_1.SiD,10),:);
bId_1_sId_18 = bId_1(ismember(bId_1.SiD,18),:);
% all BiD 2s, SiD 1,4,10,18
bId_2_sId_1 = bId_2(ismember(bId_2.SiD,1),:);
bId_2_sId_4 = bId_2(ismember(bId_2.SiD,4),:);
bId_2_sId_10 = bId_2(ismember(bId_2.SiD,10),:);
bId_2_sId_18 = bId_2(ismember(bId_2.SiD,18),:);
% all BiD 3s, SiD 1,4,10,18
bId_3_sId_1 = bId_3(ismember(bId_3.SiD,1),:);
bId_3_sId_4 = bId_3(ismember(bId_3.SiD,4),:);
bId_3_sId_10 = bId_3(ismember(bId_3.SiD,10),:);
bId_3_sId_18 = bId_3(ismember(bId_3.SiD,18),:);
% all BiD 4s, SiD 1,4,10,18
bId_4_sId_1 = bId_4(ismember(bId_4.SiD,1),:);
bId_4_sId_4 = bId_4(ismember(bId_4.SiD,4),:);
bId_4_sId_10 = bId_4(ismember(bId_4.SiD,10),:);
bId_4_sId_18 = bId_4(ismember(bId_4.SiD,18),:);
% all BiD 5s, SiD 1,4,10,18
bId_5_sId_1 = bId_5(ismember(bId_5.SiD,1),:);
bId_5_sId_4 = bId_5(ismember(bId_5.SiD,4),:);
bId_5_sId_10 = bId_5(ismember(bId_5.SiD,10),:);
bId_5_sId_18 = bId_5(ismember(bId_5.SiD,18),:);
% all BiD 6s, SiD 1,4,10,18
bId_6_sId_1 = bId_6(ismember(bId_6.SiD,1),:);
bId_6_sId_4 = bId_6(ismember(bId_6.SiD,4),:);
bId_6_sId_10 = bId_6(ismember(bId_6.SiD,10),:);
bId_6_sId_18 = bId_6(ismember(bId_6.SiD,18),:);
% all BiD 7s, SiD 1,4,10,18
bId_7_sId_1 = bId_7(ismember(bId_7.SiD,1),:);
bId_7_sId_4 = bId_7(ismember(bId_7.SiD,4),:);
bId_7_sId_10 = bId_7(ismember(bId_7.SiD,10),:);
bId_7_sId_18 = bId_7(ismember(bId_7.SiD,18),:);

Solution

  • You should generally try to avoid dynamic variable names like bId_2_sId_1, they quickly become a nightmare to maintain, and it becomes hard to do simple things like loop over similar variables. One approach would be to use a struct:

    % Convert struct to table since they're easier to extract rows from
    light_data = struct2table( light_data, 'AsArray', true );
    
    % Set up output structure
    light_data_grouped = struct();
    % Loop over each unique pair of BiD and SiD
    [grp, uBiD, uSiD] = findgroups(light_data.BiD, light_data.SiD); 
    for g = 1:max(grp)
        % Generate the struct field names
        BiDstr = sprintf('BiD_%d', uBiD(g));
        SiDstr = sprintf('SiD_%d', uSiD(g));
        % Make sure the BiD field exists
        if ~isfield( light_data_grouped, BiDstr )
            light_data_grouped.(BiDstr) = struct();
        end
        % Assign table subset into the SiD sub-field
        light_data_grouped.(BiDstr).(SiDstr) = light_data(g == grp, 'Measured_Signal');
    end
    

    Using the same example input shown in your question, this gives the following outputs:

    outputs

    So you can easily access the elements you want using something like light_data_grouped.BiD_0.SiD_1;, or as in the above code you can loop over the groups or BiD and SiD values independently.

    If you want the fields to exist even if there are no corresponding rows in the data table then you can loop over all values of both BiD and SiD and simply extract the table subset, which could be empty. The approach is similar and we still output a struct:

    % Set up output structure
    light_data_grouped = struct();
    % Loop over each unique pair of BiD and SiD
    uBiD = 0:7;
    uSiD = [1,4,10,18];
    for BiD = uBiD
        BiDstr = sprintf('BiD_%d', BiD);
        if ~isfield( light_data_grouped, BiDstr )
            light_data_grouped.(BiDstr) = struct();
        end
        
        % Get rows with this BiD
        idx_BiD = light_data.BiD == BiD;
        
        for SiD = uSiD
            SiDstr = sprintf('SiD_%d', SiD);
            
            % Get rows with this SiD
            idx_SiD = light_data.SiD == SiD;
            
            % Get subset for BiD and SiD combo (could be empty)
            light_data_grouped.(BiDstr).(SiDstr) = light_data(idx_BiD & idx_SiD, 'Measured_Signal');
        end
    end
    

    Output:

    2nd output with empty tables