Matlab Code – Why you should use structs to store and analyze data

Structures in Matlab are a great tool for organizing, referencing, and analyzing your data. They allow you to dynamically access your data in ways that you can’t with numerical or cell arrays. Follow along with the below code and comments to see how to use structs when working with data in Matlab.

%% Understanding Struct
% Blake Porter
% April 5th, 2019

% Struct's are a data format in matlab that can store [numerical data] and {cell arrays} in
% one convenient and accessible location 
% Structs can also be dynamically referenced, meaning you can create new structs easily based on the name of things
% For example, rather than making three variables for three treatment groups, you can make one struct that stores all three. Then you could easily
% add a 4th group by say, referencing a {list} of your group names

% Structs are also intuitvely structured and can have hierarchy, sort of like folders in your computer just rather than using \, you use . 
% E:\Blake_Porter_Neuro\Matlab
% would be 
% E.Blake_Porter_Neuro.Matlab

% Let's say we have three groups and we measure firing rates of neurons 
% 1: Control
% 2: Treatment 1
% 3: Treatment 2

%% Without using structs, you may do something like create a variable for each group and store the data inside:

ctrl_FR = [];
t1_FR = [];
t2_FR = [];

%% Or let's do the same, but with some dummy data

ctrl_FR = rand(25,1)+1; % 25 rows, 1 column 
t1_FR = rand(25,1)+2; % pretend its 25 trials from 1 neuron 
t2_FR = rand(25,1)+3; % and each element is the firing rate for that trial
                      % +1 and +2 to make them a little different 

%% So this is okay, but if we want to start doing some analysis, we'd have to do everything three times on three different lines
% For example, getting the mean firing rate

ctrl_mFR = mean(ctrl_FR);
t1_FR = mean(t1_FR);
t2_FR = mean(t2_FR);

% That's pretty annoying, and we only have three groups! 

%% Lets use structs instead 

% First we will get the name of what we want our struct fields (sort of like folders on your computer) to be called
groups = {'ctrl' 't1' 't2'};

%% now we will initialize our struct 
data = struct;

%% data is currently empty, so now we can start to fill it with our data
% but here is where the dynamic referencing comes into play
% we can loop through our groups list to access our different groups
 
for currGroup = 1:length(groups) % for each name in groups
    data.(groups{currGroup}).FR = rand(25,1)+currGroup; % when using dynamic referencing in structs
                                                        % enclose everything in normal (parethesis)
                                                        % then put your desired name inside as a string 
                                                        % here we loop through groups and asign them their fake firing rates
                                                        % note I am only doing +currGroup to make them different
                                                        % normally here, you may be doing something like importing from neuralynx 
    
end % for each group

%% Now our data struct has three fields, one for each group in groups
% within each field is the dummy firing rates

% For analysis we can keep looping through groups now instead of writing out a line for every group
% For example, calculate the mean firing rates

for currGroup = 1:length(groups) % for each name in groups
    data.(groups{currGroup}).mFR = mean(data.(groups{currGroup}).FR); 
    
end % for each group

%% Structs now make it trivial to use code from on experiment to another 
% for example, what if we run a similar experiment but with three treatments?
% rather than have to go back and add extra lines of code everywhere for t3 
% we just add it to our groups list and run it again

% This:
ctrl_FR = rand(25,1)+1; 
t1_FR = rand(25,1)+2; 
t2_FR = rand(25,1)+3; 
t3_FR = rand(25,1)+4;

ctrl_mFR = mean(ctrl_FR);
t1_FR = mean(t1_FR);
t2_FR = mean(t2_FR);
t3_FR = mean(t3_FR);

% Becomes this:
groups = {'ctrl' 't1' 't2' 't3'};

for currGroup = 1:length(groups) % for each name in groups
    data.(groups{currGroup}).FR = rand(25,1)+currGroup; 
    data.(groups{currGroup}).mFR = mean(data.(groups{currGroup}).FR); % combined in the same loop
end % for each group

%% This is also great for things like stats 

for currGroup = 1:length(groups) % for each name in groups
    data.all(:,currGroup) = data.(groups{currGroup}).FR; % asign each column in data.all to a group
end % for each group

[p,tbl,stats] = anova1(data.all);

%% And graphing

figure;
hold on
for currGroup = 1:length(groups) % for each name in groups
    xLoc(1:length(data.(groups{currGroup}).FR)) = currGroup; % x location on graph
    scatter(xLoc,data.(groups{currGroup}).FR,'filled'); % graph each group
    line([currGroup-0.2 currGroup+0.2],[data.(groups{currGroup}).mFR data.(groups{currGroup}).mFR],'LineStyle','-','color','k','linewidth',2)
    errorbar(currGroup,data.(groups{currGroup}).mFR,std(data.(groups{currGroup}).FR)/sqrt(length(data.(groups{currGroup}).FR)),'k','capsize',10,'linewidth',2);
end % for each group
hold off
xlim([0 5])

%%
clear

%% Structs can get even more powerful as you use more levels of them
% for example we could have multiple brains regions and multiple frequencey bands we are interested in

regions = {'ACC' 'AI' 'VTA'}; % brain regions we recorded from
bandList = [0.1 4; 7 12; 15 35; 40 100]; % frequency bands of interest

data = struct;

%% Create dummy LFP traces
for currRegion = 1:length(regions)    
        data.(regions{currRegion}).LFP = rand(1,10000)-0.5;
        
end % curr region

%% Filter LFPs based on band lists
Fs = 1000; 

for currRegion = 1:length(regions)
    for currBand = 1:length(bandList(:,1))
        Wn = [bandList(currBand,1) bandList(currBand,2)]/(Fs/2);
        
        if bandList(currBand,1) <= 4 % order cant be high when fq band is low or shit fucks up
            order = 1;
        else
            order = 3;
        end
        
        [b,a] = butter(order,Wn); % design filter
        
        % Filter
        data.(regions{currRegion}).(['Band',num2str(currBand)]).fLFP = filtfilt(b,a,data.(regions{currRegion}).LFP);
        
        % Power
        hilb =hilbert(data.(regions{currRegion}).(['Band',num2str(currBand)]).fLFP);
        hilb = imag(hilb);                                  
        data.(regions{currRegion}).(['Band',num2str(currBand)]).amp=sqrt(data.(regions{currRegion}).(['Band',num2str(currBand)]).fLFP.^2 + hilb.^2);                     
        data.(regions{currRegion}).(['Band',num2str(currBand)]).phase= atan2(hilb,data.(regions{currRegion}).(['Band',num2str(currBand)]).fLFP);                            
        
    end % curr band
end % curr region

%% Graph
for currRegion = 1:length(regions)
    figure;
    hold on
    plot(data.(regions{currRegion}).LFP)
    for currBand = 1:length(bandList(:,1))
       plot(data.(regions{currRegion}).(['Band',num2str(currBand)]).fLFP+currBand) % + currBand just moves it up the y Axis
        
    end % currBand
    hold off
    title([regions{currRegion}])
    yticks([0 1 2 3 4])
    yticklabels({'raw' [num2str(bandList(1,1)),'-',num2str(bandList(1,2))] [num2str(bandList(2,1)),'-',num2str(bandList(2,2))] [num2str(bandList(3,1)),'-',num2str(bandList(3,2))] [num2str(bandList(4,1)),'-',num2str(bandList(4,2))]});
    
end % curr Region

%% As before, since everything is dynamic referencing, we could simply add another brain region or take away a frequency band and all of this code will still work! 
regions = {'ACC' 'AI' 'VTA' 'HPC'}; % brain regions we recorded from
bandList = [7 12; 15 35; 40 100; 150 250]; % frequency bands of interest

 

Creative Commons License
This work by Blake Porter is licensed under a Creative Commons Attribution-Non Commercial-ShareAlike 4.0 International License

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.