clustGRP() - Tests the null hypothesis that the grand average voltage of a between-subject difference wave or ERP averaged across two groups is mu (usually 0) using a cluster-based permutation test and the "cluster mass" statistic (Bullmore et al., 1999; Maris & Oostenveld, 2007). Note, mu is assumed to be 0 by default. This function requires individual subject ERPs to be stored in a "GRP" structure and outputs the test results in a number of graphical and text formats. For analogous within-subject comparisons use the function clustGND.m. Note, when applied to a bin that is the mean ERP across two groups (i.e., NOT a difference wave), a one-sample test is executed. Usage: >> [GRP, prm_pval, data_t]=clustGRP(GRP_or_fname,bin,varargin); Required Inputs: GRP_or_fname - A GRP structure variable or the filename of a GRP structure that has been saved to disk. A GRP variable is based on GND variables. To create a GRP variable from GND variables use GNDs2GRP.m. See Mass Univariate ERP Toolbox documentation for detailed information about the format of a GRP variable. If you specifiy a filename be sure to include the file's path, unless the file is in the current working directory. bin - [integer] The bin to contrast against the mean of the null hypothesis. Use the function headinfo.m to see what bins are stored in a GRP variable. Use the function bin_opGRP.m to create a difference wave between two bins whose significance you can test with this function. Optional Inputs: tail - [1 | 0 | -1] An integer specifying the tail of the hypothesis test. "1" means upper-tailed (i.e., alternative hypothesis is that the ERP/difference wave is greater than mu). "0" means two-tailed (i.e., alternative hypothesis is that the ERP/difference wave is not equal to mu). "-1" means lower-tailed (i.e., alternative hypothesis is that the ERP/difference wave is less than mu). {default: 0} alpha - A number between 0 and 1 specifying the family-wise alpha level of the test. {default: 0.05} thresh_p - The test-wise p-value threshold for cluster inclusion. If a channel/time-point has a t-score that corresponds to an uncorrected p-value greater than thresh_p, it is assigned a p-value of 1 and not considered for clustering. Note that thresh_p automatically takes into account the tail of the test (e.g., you will get positive and negative t-score thresholds for a two-tailed test). chan_hood - A scalar or a 2D symmetric binary matrix that indicates which channels are considered neighbors of other channels. E.g., if chan_hood(2,10)=1, then Channel 2 and Channel 10 are nieghbors. You can produce a chan_hood matrix using the function spatial_neighbors.m. If a scalar is provided, then all electrodes within that distance of a particular electrode are considered neighbors. Note, EEGLAB's electrode coordinates assume the head has a radius of 1. See the help documentation of the function spatial_neighbors to see how you could convert this distance threshold to centimeters. {default: 0.61} head_radius - The radius of the head in whatever units the Cartesian coordinates in GRP.chanlocs are in. This is used to convert scalar values of chan_hood into centimeters. {default: []} n_perm - The number of permutations to use in the test. As this value increases, the test takes longer to compute and the results become more reliable. Manly (1997) suggests using at least 1000 permutations for an alpha level of 0.05 and at least 5000 permutations for an alpha level of 0.01. {default: 2500} time_wind - Pair of time values specifying the beginning and end of a time window in ms (e.g., [160 180]). Every single time point in the time window will be individually tested (i.e., maximal temporal resolution) if mean_wind option is NOT used. Note, boundaries of time window may not exactly correspond to desired time window boundaries because of temporal digitization (i.e., you only have samples every so many ms). {default: 0 ms to the end of the epoch} mean_wind - ['yes' or 'no'] If 'yes', the permutation test will be performed on the mean amplitude within the time window specified by time_wind. This sacrifices temporal resolution to increase test power by reducing the number of comparisons. If 'no', every single time point within time_wind's time windows will be tested individually. {default: 'no'} null_mean - [number] The mean of the null hypothesis (in units of microvolts). {default: 0} exclude_chans - A cell array of channel labels to exclude from the permutation test (e.g., {'A2','lle','rhe'}). This option sacrifices spatial resolution to increase test power by reducing the number of comparisons. Use headinfo.m to see the channel labels stored in the GRP variable. You cannot use both this option and 'include_chans' (below).{default: not used, all channels included in test} include_chans - A cell array of channel labels to use in the permutation test (e.g., {'A2','lle','rhe'}). All other channels will be ignored. This option sacrifices spatial resolution to increase test power by reducing the number of comparisons. Use headinfo.m to see the channel labels stored in the GRP variable. You cannot use both this option and 'exclude_chans' (above). {default: not used, all channels included in test} verblevel - An integer specifiying the amount of information you want this function to provide about what it is doing during runtime. Options are: 0 - quiet, only show errors, warnings, and EEGLAB reports 1 - stuff anyone should probably know 2 - stuff you should know the first time you start working with a data set {default value} 3 - stuff that might help you debug (show all reports) plot_gui - ['yes' or 'no'] If 'yes', a GUI is created for visualizing the results of the permutation test using the function gui_erp.m. The GUI vizualizes the grand average ERPs in each bin via various stats (uV, t-scores), shows topographies at individual time points, and illustrates which electrodes significantly differ from the null hypothesis. This option does not work if mean_wind option is set to 'yes.' This GUI can be reproduced using the function gui_erp.m. {default: 'yes'} plot_raster - ['yes' or 'no'] If 'yes', a two-dimensional (time x channel) binary "raster" diagram is created to illustrate the results of the permutation test. Significant negative and positive deviations from the null hypothesis are shown as black and white rectangles respectively. Non- significant comparisons are shown as gray rectangles. Clicking on the rectangles will show you the corresponding time and channel label for that rectangle. This figure can be reproduced with the function sig_raster.m. {default: 'yes'} plot_mn_topo - ['yes' or 'no'] If 'yes', the topography of the mean voltages/effects in the time window is produced. More specifically, two figures are produced: one showing the topographies in uV the other in t-scores. Significant/ nonsignificant comparisons are shown as white/black electrodes. Clicking on electrodes will show the electrode's name. This figure can be reproduced with the function sig_topo.m. This option has NO effect if mean_wind option is set to 'no'. {default: 'yes'} output_file - A string indicating the name of a space delimited text file to produce containing the p-values of all comparisons and the details of the test (e.g., number of permutations, family-wise alpha level, etc...). If mean_wind option is set to 'yes,' t-scores of each comparison are also included since you cannot derive them from the t-scores at each time point/electrode in a simple way. When importing this file into a spreadsheet be sure NOT to count consecutive spaces as multiple delimiters. {default: none} save_GRP - ['yes' or 'no'] If 'yes', the GRP variable will be saved to disk after the permutation test is completed and added to it. User will first be prompted to verify file name and path. {default: 'yes'} reproduce_test- [integer] The number of the permutation test stored in the GRP variable to reproduce. For example, if 'reproduce_test' equals 2, the second t-test stored in the GRP variable (i.e., GRP.t_tests(2)) will be reproduced. Reproduction is accomplished by setting the random number generator used in the permutation test to the same initial state it was in when the permutation test was first applied. Outputs: GRP - GRP structure variable. This is the same as the input GRP variable with one addition: the field GRP.t_tests will contain the results of the permutation test and the test parameters. prm_pval - A two-dimensional matrix (channel x time) of the p-values of each comparison. data_t - A two-dimensional matrix (channel x time) of the t-scores of each comparison. Note also that a great deal of information about the test is displayed in the MATLAB command window. You can easiy record of all this information to a text file using the MATLAB command "diary." Global Variables: VERBLEVEL = Mass Univariate ERP Toolbox level of verbosity (i.e., tells functions how much to report about what they're doing during runtime) set by the optional function argument 'verblevel' Notes: -To add a difference wave to a GRP variable, use the function "bin_opGRP". -Unlike a parametric test (e.g., an ANOVA), a discrete set of p-values are possible (at most the number of possible permutations). Since the number of possible permutations grows rapdily with the number of participants, this is only issue for small sample sizes (e.g., 3 participants in each group). When you have such a small sample size, the limited number of p-values may make the test overly conservative (e.g., you might be forced to use an alpha level of .0286 since it is the biggest possible alpha level less than .05). Author: David Groppe May, 2011 Kutaslab, San Diego References: Bullmore, E. T., Suckling, J., Overmeyer, S., Rabe-Hesketh, S., Taylor, E., & Brammer, M. J. (1999). Global, voxel, and cluster tests, by theory and permutation, for a difference between two groups of structural MR images of the brain. IEEE Transactions on Medical Imaging, 18(1), 32-42. doi:10.1109/42.750253 Maris, E., & Oostenveld, R. (2007). Nonparametric statistical testing of EEG- and MEG-data. Journal of Neuroscience Methods, 164(1), 177-190. doi:10.1016/j.jneumeth.2007.03.024 Manly, B.F.J. (1997) Randomization, bootstrap, and Monte Carlo methods in biology. 2nd ed. Chapmn and Hall, London.
0001 % clustGRP() - Tests the null hypothesis that the grand average voltage 0002 % of a between-subject difference wave or ERP averaged across two 0003 % groups is mu (usually 0) using a cluster-based 0004 % permutation test and the "cluster mass" statistic 0005 % (Bullmore et al., 1999; Maris & Oostenveld, 2007). Note, mu 0006 % is assumed to be 0 by default. This function requires 0007 % individual subject ERPs to be stored in a "GRP" structure 0008 % and outputs the test results in a number of graphical 0009 % and text formats. For analogous within-subject comparisons use 0010 % the function clustGND.m. 0011 % Note, when applied to a bin that is the mean ERP across two 0012 % groups (i.e., NOT a difference wave), a one-sample test is 0013 % executed. 0014 % 0015 % 0016 % 0017 % Usage: 0018 % >> [GRP, prm_pval, data_t]=clustGRP(GRP_or_fname,bin,varargin); 0019 % 0020 % Required Inputs: 0021 % GRP_or_fname - A GRP structure variable or the filename of a GRP 0022 % structure that has been saved to disk. A GRP variable 0023 % is based on GND variables. To create a GRP variable from 0024 % GND variables use GNDs2GRP.m. See Mass Univariate ERP 0025 % Toolbox documentation for detailed information about the 0026 % format of a GRP variable. If you specifiy a filename be 0027 % sure to include the file's path, unless the file is in 0028 % the current working directory. 0029 % bin - [integer] The bin to contrast against the mean of the 0030 % null hypothesis. Use the function headinfo.m to see what 0031 % bins are stored in a GRP variable. Use the function 0032 % bin_opGRP.m to create a difference wave between two bins 0033 % whose significance you can test with this function. 0034 % 0035 % Optional Inputs: 0036 % tail - [1 | 0 | -1] An integer specifying the tail of the 0037 % hypothesis test. "1" means upper-tailed (i.e., alternative 0038 % hypothesis is that the ERP/difference wave is greater 0039 % than mu). "0" means two-tailed (i.e., alternative hypothesis 0040 % is that the ERP/difference wave is not equal to mu). 0041 % "-1" means lower-tailed (i.e., alternative hypothesis 0042 % is that the ERP/difference wave is less than mu). 0043 % {default: 0} 0044 % alpha - A number between 0 and 1 specifying the family-wise 0045 % alpha level of the test. {default: 0.05} 0046 % thresh_p - The test-wise p-value threshold for cluster inclusion. If 0047 % a channel/time-point has a t-score that corresponds to an 0048 % uncorrected p-value greater than thresh_p, it is assigned 0049 % a p-value of 1 and not considered for clustering. Note 0050 % that thresh_p automatically takes into account the tail of 0051 % the test (e.g., you will get positive and negative t-score 0052 % thresholds for a two-tailed test). 0053 % chan_hood - A scalar or a 2D symmetric binary matrix that indicates 0054 % which channels are considered neighbors of other 0055 % channels. E.g., if chan_hood(2,10)=1, then Channel 2 0056 % and Channel 10 are nieghbors. You can produce a 0057 % chan_hood matrix using the function spatial_neighbors.m. 0058 % If a scalar is provided, then all electrodes within that 0059 % distance of a particular electrode are considered 0060 % neighbors. Note, EEGLAB's electrode coordinates assume 0061 % the head has a radius of 1. See the help documentation 0062 % of the function spatial_neighbors to see how you could 0063 % convert this distance threshold to centimeters. 0064 % {default: 0.61} 0065 % head_radius - The radius of the head in whatever units the Cartesian 0066 % coordinates in GRP.chanlocs are in. This is used to 0067 % convert scalar values of chan_hood into centimeters. 0068 % {default: []} 0069 % n_perm - The number of permutations to use in the test. As this 0070 % value increases, the test takes longer to compute and 0071 % the results become more reliable. Manly (1997) suggests 0072 % using at least 1000 permutations for an alpha level of 0073 % 0.05 and at least 5000 permutations for an alpha level 0074 % of 0.01. {default: 2500} 0075 % time_wind - Pair of time values specifying the beginning and 0076 % end of a time window in ms (e.g., [160 180]). Every 0077 % single time point in the time window will be individually 0078 % tested (i.e., maximal temporal resolution) if mean_wind 0079 % option is NOT used. Note, boundaries of time window 0080 % may not exactly correspond to desired time window 0081 % boundaries because of temporal digitization (i.e., you 0082 % only have samples every so many ms). {default: 0 ms to 0083 % the end of the epoch} 0084 % mean_wind - ['yes' or 'no'] If 'yes', the permutation test will be 0085 % performed on the mean amplitude within the time window 0086 % specified by time_wind. This sacrifices temporal 0087 % resolution to increase test power by reducing the number 0088 % of comparisons. If 'no', every single time point within 0089 % time_wind's time windows will be tested individually. 0090 % {default: 'no'} 0091 % null_mean - [number] The mean of the null hypothesis (in units of 0092 % microvolts). {default: 0} 0093 % exclude_chans - A cell array of channel labels to exclude from the 0094 % permutation test (e.g., {'A2','lle','rhe'}). This option 0095 % sacrifices spatial resolution to increase test power by 0096 % reducing the number of comparisons. Use headinfo.m to see 0097 % the channel labels stored in the GRP variable. You cannot 0098 % use both this option and 'include_chans' (below).{default: 0099 % not used, all channels included in test} 0100 % include_chans - A cell array of channel labels to use in the permutation 0101 % test (e.g., {'A2','lle','rhe'}). All other channels will 0102 % be ignored. This option sacrifices spatial resolution to 0103 % increase test power by reducing the number of comparisons. 0104 % Use headinfo.m to see the channel labels stored in the GRP 0105 % variable. You cannot use both this option and 0106 % 'exclude_chans' (above). {default: not used, all channels 0107 % included in test} 0108 % verblevel - An integer specifiying the amount of information you want 0109 % this function to provide about what it is doing during runtime. 0110 % Options are: 0111 % 0 - quiet, only show errors, warnings, and EEGLAB reports 0112 % 1 - stuff anyone should probably know 0113 % 2 - stuff you should know the first time you start working 0114 % with a data set {default value} 0115 % 3 - stuff that might help you debug (show all 0116 % reports) 0117 % plot_gui - ['yes' or 'no'] If 'yes', a GUI is created for 0118 % visualizing the results of the permutation test using the 0119 % function gui_erp.m. The GUI vizualizes the grand average 0120 % ERPs in each bin via various stats (uV, t-scores), shows 0121 % topographies at individual time points, and illustrates 0122 % which electrodes significantly differ from the null 0123 % hypothesis. This option does not work if mean_wind 0124 % option is set to 'yes.' This GUI can be reproduced using 0125 % the function gui_erp.m. {default: 'yes'} 0126 % plot_raster - ['yes' or 'no'] If 'yes', a two-dimensional (time x channel) 0127 % binary "raster" diagram is created to illustrate the 0128 % results of the permutation test. Significant negative and 0129 % positive deviations from the null hypothesis are shown 0130 % as black and white rectangles respectively. Non- 0131 % significant comparisons are shown as gray rectangles. 0132 % Clicking on the rectangles will show you the 0133 % corresponding time and channel label for that 0134 % rectangle. This figure can be reproduced with the 0135 % function sig_raster.m. {default: 'yes'} 0136 % plot_mn_topo - ['yes' or 'no'] If 'yes', the topography of the mean 0137 % voltages/effects in the time window is produced. More 0138 % specifically, two figures are produced: one showing the 0139 % topographies in uV the other in t-scores. Significant/ 0140 % nonsignificant comparisons are shown as white/black 0141 % electrodes. Clicking on electrodes will show the 0142 % electrode's name. This figure can be reproduced with 0143 % the function sig_topo.m. This option has NO effect if 0144 % mean_wind option is set to 'no'. {default: 'yes'} 0145 % output_file - A string indicating the name of a space delimited text 0146 % file to produce containing the p-values of all comparisons 0147 % and the details of the test (e.g., number of permutations, 0148 % family-wise alpha level, etc...). If mean_wind option is 0149 % set to 'yes,' t-scores of each comparison are also 0150 % included since you cannot derive them from the t-scores 0151 % at each time point/electrode in a simple way. When 0152 % importing this file into a spreadsheet be sure NOT to count 0153 % consecutive spaces as multiple delimiters. {default: none} 0154 % save_GRP - ['yes' or 'no'] If 'yes', the GRP variable will be 0155 % saved to disk after the permutation test is completed 0156 % and added to it. User will first be prompted to verify 0157 % file name and path. {default: 'yes'} 0158 % reproduce_test- [integer] The number of the permutation test stored in 0159 % the GRP variable to reproduce. For example, if 0160 % 'reproduce_test' equals 2, the second t-test 0161 % stored in the GRP variable (i.e., GRP.t_tests(2)) will 0162 % be reproduced. Reproduction is accomplished by setting 0163 % the random number generator used in the permutation test 0164 % to the same initial state it was in when the permutation 0165 % test was first applied. 0166 % 0167 % Outputs: 0168 % GRP - GRP structure variable. This is the same as 0169 % the input GRP variable with one addition: the 0170 % field GRP.t_tests will contain the results of the 0171 % permutation test and the test parameters. 0172 % prm_pval - A two-dimensional matrix (channel x time) of the 0173 % p-values of each comparison. 0174 % data_t - A two-dimensional matrix (channel x time) of the 0175 % t-scores of each comparison. 0176 % 0177 % Note also that a great deal of information about the test is displayed 0178 % in the MATLAB command window. You can easiy record of all this 0179 % information to a text file using the MATLAB command "diary." 0180 % 0181 % Global Variables: 0182 % VERBLEVEL = Mass Univariate ERP Toolbox level of verbosity (i.e., tells 0183 % functions how much to report about what they're doing during 0184 % runtime) set by the optional function argument 'verblevel' 0185 % 0186 % Notes: 0187 % -To add a difference wave to a GRP variable, use the function "bin_opGRP". 0188 % 0189 % -Unlike a parametric test (e.g., an ANOVA), a discrete set of p-values 0190 % are possible (at most the number of possible permutations). Since the 0191 % number of possible permutations grows rapdily with the number of 0192 % participants, this is only issue for small sample sizes (e.g., 3 0193 % participants in each group). When you have such a small sample size, the 0194 % limited number of p-values may make the test overly conservative (e.g., 0195 % you might be forced to use an alpha level of .0286 since it is the biggest 0196 % possible alpha level less than .05). 0197 % 0198 % 0199 % Author: 0200 % David Groppe 0201 % May, 2011 0202 % Kutaslab, San Diego 0203 % 0204 % References: 0205 % Bullmore, E. T., Suckling, J., Overmeyer, S., Rabe-Hesketh, S., Taylor, 0206 % E., & Brammer, M. J. (1999). Global, voxel, and cluster tests, by theory 0207 % and permutation, for a difference between two groups of structural MR 0208 % images of the brain. IEEE Transactions on Medical Imaging, 18(1), 32-42. 0209 % doi:10.1109/42.750253 0210 % 0211 % Maris, E., & Oostenveld, R. (2007). Nonparametric statistical testing of 0212 % EEG- and MEG-data. Journal of Neuroscience Methods, 164(1), 177-190. 0213 % doi:10.1016/j.jneumeth.2007.03.024 0214 % 0215 % Manly, B.F.J. (1997) Randomization, bootstrap, and Monte Carlo methods in 0216 % biology. 2nd ed. Chapmn and Hall, London. 0217 0218 %%%%%%%%%%%%%%%% REVISION LOG %%%%%%%%%%%%%%%%% 0219 % 0220 % 12/11/2011-Now uses Amy Guthormsen's recursiveless find_clusters.m. 0221 % 0222 0223 0224 function [GRP, prm_pval, data_t]=clustGRP(GRP_or_fname,bin,varargin) 0225 0226 global VERBLEVEL; 0227 0228 p=inputParser; 0229 p.addRequired('GRP_or_fname',@(x) ischar(x) || isstruct(x)); 0230 p.addRequired('bin',@(x) isnumeric(x) && (length(x)==1) && (x>0)); 0231 p.addParamValue('tail',0,@(x) isnumeric(x) && (length(x)==1)); 0232 p.addParamValue('alpha',0.05,@(x) isnumeric(x) && (x>0) && (x<1)); 0233 p.addParamValue('thresh_p',0.05,@(x) (length(x)==1) && isnumeric(x) && (x>0) && (x<1)); 0234 p.addParamValue('chan_hood',0.61,@isnumeric); 0235 p.addParamValue('head_radius',[],@isnumeric); 0236 p.addParamValue('time_wind',[],@(x) isnumeric(x) && (size(x,2)==2)); 0237 p.addParamValue('mean_wind','no',@(x) ischar(x) && (strcmpi(x,'yes') || strcmpi(x,'no'))); 0238 p.addParamValue('n_perm',2500,@(x) isnumeric(x) && (length(x)==1)); 0239 p.addParamValue('null_mean',0,@(x) isnumeric(x) && (length(x)==1)); 0240 p.addParamValue('verblevel',[],@(x) isnumeric(x) && (length(x)==1)); 0241 p.addParamValue('exclude_chans',[],@(x) ischar(x) || iscell(x)); 0242 p.addParamValue('include_chans',[],@(x) ischar(x) || iscell(x)); 0243 p.addParamValue('plot_gui','yes',@(x) ischar(x) && (strcmpi(x,'yes') || strcmpi(x,'no'))); 0244 p.addParamValue('plot_raster','yes',@(x) ischar(x) && (strcmpi(x,'yes') || strcmpi(x,'no'))); 0245 p.addParamValue('plot_mn_topo',[],@(x) ischar(x) && (strcmpi(x,'yes') || strcmpi(x,'no'))); 0246 p.addParamValue('output_file',[],@ischar); 0247 p.addParamValue('reproduce_test',[],@(x) isnumeric(x) && (length(x)==1)); 0248 p.addParamValue('save_GRP','yes',@(x) ischar(x) && (strcmpi(x,'yes') || strcmpi(x,'no'))); 0249 0250 p.parse(GRP_or_fname,bin,varargin{:}); 0251 0252 if isempty(p.Results.verblevel), 0253 if isempty(VERBLEVEL), 0254 VERBLEVEL=2; 0255 end 0256 else 0257 VERBLEVEL=p.Results.verblevel; 0258 end 0259 0260 mean_wind=str2bool(p.Results.mean_wind); 0261 0262 %Load GRP struct 0263 if ischar(GRP_or_fname), 0264 fprintf('Loading GRP struct from file %s.\n',GRP_or_fname); 0265 load(GRP_or_fname,'-MAT'); 0266 else 0267 GRP=GRP_or_fname; 0268 clear GRP_or_fname; 0269 end 0270 [n_chan, n_pt, n_bin]=size(GRP.grands); 0271 n_group=length(GRP.GND_fnames); 0272 VerbReport(sprintf('Experiment: %s',GRP.exp_desc),2,VERBLEVEL); 0273 0274 if (bin>n_bin), 0275 error('There is no Bin %d in this GRP variable.',bin); 0276 end 0277 grpA=GRP.bin_info(bin).groupA; 0278 grpB=GRP.bin_info(bin).groupB; 0279 0280 0281 %% Figure out which channels to ignore if any 0282 %But first make sure exclude & include options were not both used. 0283 if ~isempty(p.Results.include_chans) && ~isempty(p.Results.exclude_chans) 0284 error('You cannot use BOTH ''include_chans'' and ''exclude_chans'' options.'); 0285 end 0286 if ischar(p.Results.exclude_chans), 0287 exclude_chans{1}=p.Results.exclude_chans; 0288 elseif isempty(p.Results.exclude_chans) 0289 exclude_chans=[]; 0290 else 0291 exclude_chans=p.Results.exclude_chans; 0292 end 0293 if ischar(p.Results.include_chans), 0294 include_chans{1}=p.Results.include_chans; 0295 elseif isempty(p.Results.include_chans) 0296 include_chans=[]; 0297 else 0298 include_chans=p.Results.include_chans; 0299 end 0300 0301 % exclude and include chan options 0302 if ~isempty(exclude_chans), 0303 ignore_chans=zeros(1,length(exclude_chans)); %preallocate mem 0304 ct=0; 0305 for x=1:length(exclude_chans), 0306 found=0; 0307 for c=1:n_chan, 0308 if strcmpi(exclude_chans{x},GRP.chanlocs(c).labels), 0309 found=1; 0310 ct=ct+1; 0311 ignore_chans(ct)=c; 0312 end 0313 end 0314 if ~found, 0315 watchit(sprintf('I attempted to exclude %s. However no such electrode was found in GRP variable.', ... 0316 exclude_chans{x})); 0317 end 0318 end 0319 ignore_chans=ignore_chans(1:ct); 0320 use_chans=setdiff(1:n_chan,ignore_chans); 0321 elseif ~isempty(include_chans), 0322 use_chans=zeros(1,length(include_chans)); %preallocate mem 0323 ct=0; 0324 for x=1:length(include_chans), 0325 found=0; 0326 for c=1:n_chan, 0327 if strcmpi(include_chans{x},GRP.chanlocs(c).labels), 0328 found=1; 0329 ct=ct+1; 0330 use_chans(ct)=c; 0331 end 0332 end 0333 if ~found, 0334 watchit(sprintf('I attempted to include %s. However no such electrode was found in GRP variable.', ... 0335 include_chans{x})); 0336 end 0337 end 0338 use_chans=use_chans(1:ct); 0339 else 0340 use_chans=1:n_chan; 0341 end 0342 0343 % Establish spatial neighborhood matrix for making clusters 0344 if isscalar(p.Results.chan_hood), 0345 chan_hood=spatial_neighbors(GRP.chanlocs(use_chans),p.Results.chan_hood); 0346 else 0347 chan_hood=p.Results.chan_hood; 0348 end 0349 0350 0351 %% Find time points 0352 if isempty(p.Results.time_wind), 0353 time_wind=[0 GRP.time_pts(end)]; %default time window 0354 else 0355 time_wind=p.Results.time_wind; 0356 end 0357 time_wind=sort(time_wind,2); %first make sure earlier of each pair of time points is first 0358 time_wind=sort(time_wind,1); %next sort time windows from earliest to latest onset 0359 n_wind=size(time_wind,1); 0360 if n_wind>1, 0361 error('clustGRP.m can only handle a single mean time window at the moment. Try tmaxGRP.m or tfdrGRP.m if you want to simultaneously test hypotheses in multiple mean time windows.'); 0362 end 0363 0364 if mean_wind, 0365 use_tpts=cell(1,n_wind); 0366 else 0367 use_tpts=[]; 0368 end 0369 for a=1:n_wind, 0370 VerbReport(sprintf('Time Window #%d:',a),1,VERBLEVEL); 0371 VerbReport(sprintf('Attempting to use time boundaries of %d to %d ms for hypothesis test.',time_wind(a,1),time_wind(a,2)), ... 0372 1,VERBLEVEL); 0373 start_tpt=find_tpt(time_wind(a,1),GRP.time_pts); 0374 end_tpt=find_tpt(time_wind(a,2),GRP.time_pts); 0375 if mean_wind, 0376 use_tpts{a}=[start_tpt:end_tpt]; 0377 else 0378 use_tpts=[use_tpts [start_tpt:end_tpt]]; 0379 end 0380 %replace desired time points with closest matches 0381 time_wind(a,1)=GRP.time_pts(start_tpt); 0382 time_wind(a,2)=GRP.time_pts(end_tpt); 0383 VerbReport(sprintf('Exact window boundaries are %d to %d ms (that''s from time point %d to %d).', ... 0384 time_wind(a,1),time_wind(a,2),start_tpt,end_tpt),1,VERBLEVEL); 0385 end 0386 if ~mean_wind, 0387 use_tpts=unique(use_tpts); %sorts time points and gets rid of any redundant time points 0388 end 0389 0390 0391 %% Compile data from two groups of participants 0392 %pre-allocate memory 0393 grp_ct=0; 0394 for grp=[grpA grpB], 0395 grp_ct=grp_ct+1; 0396 if grp_ct==1, 0397 %Group A's bin 0398 ur_bin=GRP.bin_info(bin).source_binA; 0399 else 0400 %Group B's bin 0401 ur_bin=GRP.bin_info(bin).source_binB; 0402 end 0403 0404 %Load GND variable for this group 0405 load(GRP.GND_fnames{grp},'-MAT'); 0406 VerbReport(sprintf('Loading individual participant ERPs from Bin %d (%s) of GND variable from %s.', ... 0407 ur_bin,GND.bin_info(ur_bin).bindesc,GRP.GND_fnames{grp}),2,VERBLEVEL); 0408 0409 %Check to make sure time points are still compatible across GND and GRP 0410 %variables 0411 if ~isequal(GND.time_pts,GRP.time_pts) 0412 error('The time points in the GND variable from file %s are NOT the same as those in your GRP variable.', ... 0413 GRP.GND_fnames{grp}); 0414 end 0415 0416 %Derive channel indexes for this GND variable in case GND.chanlocs differs from 0417 %GRP.chanlocs (in order or in identity of channels) 0418 n_chanGND=length(GND.chanlocs); 0419 use_chansGND=[]; 0420 for a=use_chans, 0421 found=0; 0422 for b=1:n_chanGND, 0423 if strcmpi(GRP.chanlocs(a).labels,GND.chanlocs(b).labels), 0424 found=1; 0425 use_chansGND=[use_chansGND b]; 0426 break; 0427 end 0428 end 0429 if ~found, 0430 error('GND variable in file %s is missing channel %s.',GRP.GND_fnames{grp},GRP.chanlocs(a).labels); 0431 end 0432 end 0433 0434 %Use only subs with data in relevant bin(s) 0435 use_subs=find(GND.indiv_bin_ct(:,ur_bin)); 0436 n_sub=length(use_subs); 0437 0438 if mean_wind, 0439 %Take mean amplitude in time blocks and then test 0440 if grp_ct==1, 0441 %Group A's ERPs 0442 n_subA=n_sub; 0443 use_subsA=use_subs; 0444 erpsA=zeros(length(use_chans),n_wind,n_sub); 0445 for a=1:n_wind, 0446 for sub=1:n_sub, 0447 erpsA(:,a,sub)=mean(GND.indiv_erps(use_chansGND,use_tpts{a},ur_bin,use_subs(sub)),2); 0448 end 0449 end 0450 else 0451 %Group B's ERPs 0452 n_subB=n_sub; 0453 use_subsB=use_subs; 0454 erpsB=zeros(length(use_chans),n_wind,n_sub); 0455 for a=1:n_wind, 0456 for sub=1:n_sub, 0457 erpsB(:,a,sub)=mean(GND.indiv_erps(use_chansGND,use_tpts{a},ur_bin,use_subs(sub)),2); 0458 end 0459 end 0460 end 0461 else 0462 %Use every single time point in time window(s) 0463 if grp_ct==1, 0464 %Group A's ERPs 0465 n_subA=n_sub; 0466 use_subsA=use_subs; 0467 n_use_tpts=length(use_tpts); 0468 erpsA=zeros(length(use_chans),n_use_tpts,n_sub); 0469 for sub=1:n_sub, 0470 erpsA(:,:,sub)=GND.indiv_erps(use_chansGND,use_tpts,ur_bin,use_subs(sub)); 0471 end 0472 else 0473 %Group B's ERPs 0474 n_subB=n_sub; 0475 use_subsB=use_subs; 0476 n_use_tpts=length(use_tpts); 0477 erpsB=zeros(length(use_chans),n_use_tpts,n_sub); 0478 for sub=1:n_sub, 0479 erpsB(:,:,sub)=GND.indiv_erps(use_chansGND,use_tpts,ur_bin,use_subs(sub)); 0480 end 0481 end 0482 end 0483 end 0484 df=n_subA+n_subB-2; 0485 0486 %% Report tail of test & alpha levels 0487 VerbReport(sprintf('Testing null hypothesis that the grand average ERPs in GRP variable''s Bin %d (%s) have a mean of %f microvolts.',bin, ... 0488 GRP.bin_info(bin).bindesc,p.Results.null_mean),1,VERBLEVEL); 0489 if p.Results.tail==0 0490 VerbReport(sprintf('Alternative hypothesis is that the ERPs differ from %f (i.e., two-tailed test).',p.Results.null_mean), ... 0491 1,VERBLEVEL); 0492 elseif p.Results.tail<0, 0493 VerbReport(sprintf('Alternative hypothesis is that the ERPs are less than %f (i.e., lower-tailed test).',p.Results.null_mean), ... 0494 1,VERBLEVEL); 0495 else 0496 VerbReport(sprintf('Alternative hypothesis is that the ERPs are greater than %f (i.e., upper-tailed test).',p.Results.null_mean), ... 0497 1,VERBLEVEL); 0498 end 0499 0500 %% Optionally reset random number stream to reproduce a previous test 0501 if isempty(p.Results.reproduce_test), 0502 seed_state=[]; 0503 else 0504 if p.Results.reproduce_test>length(GRP.t_tests), 0505 error('Value of argument ''reproduce_test'' is too high. You only have %d permutation tests stored with this GRP variable.',length(GRP.t_tests)); 0506 else 0507 if isnan(GRP.t_tests(p.Results.reproduce_test).n_perm) 0508 error('t-test set %d is NOT a permutation test. You don''t need to seed the random number generator to reproduce it.', ... 0509 p.Results.reproduce_test); 0510 else 0511 seed_state=GRP.t_tests(p.Results.reproduce_test).seed_state; 0512 end 0513 end 0514 end 0515 0516 %% Compute the permutation test 0517 if strcmpi(GRP.bin_info(bin).op,'(A+B)/n'), 0518 %one sample t-test 0519 VerbReport('Performing one sample/repeated measures t-tests.',1,VERBLEVEL); 0520 erpsAB=zeros(length(use_chans),size(erpsA,2),n_subA+n_subB); 0521 erpsAB(:,:,1:n_subA)=erpsA-p.Results.null_mean; 0522 erpsAB(:,:,(n_subA+1):(n_subA+n_subB))=erpsB-p.Results.null_mean; 0523 [prm_pval, data_t, clust_info, seed_state, est_alpha]=clust_perm1(erpsAB, ... 0524 chan_hood,p.Results.n_perm,p.Results.alpha,p.Results.tail,p.Results.thresh_p, ... 0525 VERBLEVEL,seed_state,0); 0526 else 0527 %independent samples t-test 0528 VerbReport('Performing independent samples t-tests.',1,VERBLEVEL); 0529 [prm_pval, data_t, clust_info, seed_state, est_alpha]=clust_perm2(erpsA-p.Results.null_mean, ... 0530 erpsB,chan_hood,p.Results.n_perm,p.Results.alpha,p.Results.tail,p.Results.thresh_p,VERBLEVEL,seed_state,0); 0531 end 0532 0533 %% Command line summary of results 0534 if p.Results.tail>=0, 0535 %upper or two-tailed test 0536 n_pos=length(clust_info.pos_clust_pval); 0537 fprintf('# of positive clusters found: %d\n',n_pos); 0538 fprintf('# of significant positive clusters found: %d\n',sum(clust_info.pos_clust_pval<est_alpha)); 0539 fprintf('Positive cluster p-values range from %g to %g.\n',min(clust_info.pos_clust_pval), ... 0540 max(clust_info.pos_clust_pval)); 0541 end 0542 if p.Results.tail<=0, 0543 %lower or two-tailed test 0544 n_neg=length(clust_info.neg_clust_pval); 0545 fprintf('# of negative clusters found: %d\n',n_neg); 0546 fprintf('# of significant negative clusters found: %d\n',sum(clust_info.neg_clust_pval<est_alpha)); 0547 fprintf('Negative cluster p-values range from %g to %g.\n',min(clust_info.neg_clust_pval), ... 0548 max(clust_info.neg_clust_pval)); 0549 end 0550 0551 sig_ids=find(prm_pval<p.Results.alpha); 0552 if isempty(sig_ids), 0553 fprintf('ERPs are NOT significantly different from zero (alpha=%f) at any time point/window analyzed.\n', ... 0554 p.Results.alpha); 0555 fprintf('All p-values>=%f\n',min(min(prm_pval))); 0556 else 0557 [dummy min_t_id]=min(abs(data_t(sig_ids))); 0558 min_t=data_t(sig_ids(min_t_id)); 0559 VerbReport(['Smallest significant t-score(s):' num2str(min_t)],1,VERBLEVEL); 0560 if p.Results.tail 0561 %one-tailed test 0562 tw_alpha=1-cdf('t',max(abs(min_t)),n_sub-1); 0563 else 0564 %two-tailed test 0565 tw_alpha=(1-cdf('t',max(abs(min_t)),n_sub-1))*2; 0566 end 0567 0568 VerbReport(sprintf('That corresponds to a test-wise alpha level of %f.',tw_alpha),1,VERBLEVEL); 0569 VerbReport(sprintf('Bonferroni test-wise alpha would be %f.',p.Results.alpha/(size(prm_pval,1)* ... 0570 size(prm_pval,2))),1,VERBLEVEL); 0571 sig_tpts=find(sum(prm_pval<p.Results.alpha)); 0572 0573 fprintf('Significant differences from zero (in order of earliest to latest):\n'); 0574 max_sig_p=0; 0575 min_sig_p=2; 0576 for t=sig_tpts, 0577 if mean_wind 0578 %time windows instead of time points 0579 fprintf('%d to %d ms, electrode(s): ',GRP.time_pts(use_tpts{t}(1)), ... 0580 GRP.time_pts(use_tpts{t}(end))); 0581 else 0582 fprintf('%d ms, electrode(s): ',GRP.time_pts(use_tpts(t))); 0583 end 0584 sig_elec=find(prm_pval(:,t)<p.Results.alpha); 0585 ct=0; 0586 for c=sig_elec', 0587 ct=ct+1; 0588 if prm_pval(c,t)>max_sig_p, 0589 max_sig_p=prm_pval(c,t); 0590 end 0591 if prm_pval(c,t)<min_sig_p, 0592 min_sig_p=prm_pval(c,t); 0593 end 0594 0595 if ct==length(sig_elec), 0596 fprintf('%s.\n',GRP.chanlocs(use_chans(c)).labels); 0597 else 0598 fprintf('%s, ',GRP.chanlocs(use_chans(c)).labels); 0599 end 0600 end 0601 end 0602 fprintf('All significant corrected p-values are between %f and %f\n',max_sig_p,min_sig_p); 0603 end 0604 0605 0606 %Add permutation results to GRP struct 0607 n_t_tests=length(GRP.t_tests); 0608 neo_test=n_t_tests+1; 0609 GRP.t_tests(neo_test).bin=bin; 0610 GRP.t_tests(neo_test).time_wind=time_wind; 0611 GRP.t_tests(neo_test).used_tpt_ids=use_tpts; 0612 n_use_chans=length(use_chans); 0613 include_chans=cell(1,n_use_chans); 0614 for a=1:n_use_chans, 0615 include_chans{a}=GRP.chanlocs(use_chans(a)).labels; 0616 end 0617 GRP.t_tests(neo_test).include_chans=include_chans; 0618 GRP.t_tests(neo_test).used_chan_ids=use_chans; 0619 GRP.t_tests(neo_test).mult_comp_method='cluster mass perm test'; 0620 GRP.t_tests(neo_test).n_perm=p.Results.n_perm; 0621 GRP.t_tests(neo_test).desired_alphaORq=p.Results.alpha; 0622 GRP.t_tests(neo_test).estimated_alpha=est_alpha; 0623 GRP.t_tests(neo_test).null_mean=p.Results.null_mean; 0624 if mean_wind, 0625 GRP.t_tests(neo_test).data_t=data_t; 0626 GRP.t_tests(neo_test).mean_wind='yes'; 0627 else 0628 GRP.t_tests(neo_test).data_t='See GRP.grands_t'; 0629 GRP.t_tests(neo_test).mean_wind='no'; 0630 end 0631 GRP.t_tests(neo_test).crit_t=NaN; 0632 GRP.t_tests(neo_test).df=df; 0633 GRP.t_tests(neo_test).adj_pval=prm_pval; 0634 GRP.t_tests(neo_test).fdr_rej=NaN; 0635 GRP.t_tests(neo_test).seed_state=seed_state; 0636 GRP.t_tests(neo_test).clust_info=clust_info; 0637 GRP.t_tests(neo_test).chan_hood=chan_hood; 0638 0639 if strcmpi(p.Results.plot_raster,'yes'), 0640 sig_raster(GRP,neo_test,'verblevel',0,'use_color','rgb'); 0641 end 0642 0643 if mean_wind, 0644 if strcmpi(p.Results.plot_mn_topo,'yes') || isempty(p.Results.plot_mn_topo), 0645 sig_topo(GRP,neo_test,'units','t','verblevel',0); %t-score topographies 0646 sig_topo(GRP,neo_test,'units','uV','verblevel',0); %microvolt topographies 0647 end 0648 else 0649 if strcmpi(p.Results.plot_gui,'yes'), 0650 gui_erp('initialize','GNDorGRP',GRP,'t_test',neo_test,'stat','t', ... 0651 'verblevel',1); 0652 end 0653 end 0654 0655 if ~isempty(p.Results.output_file) 0656 [fid msg]=fopen(p.Results.output_file,'w'); 0657 if fid==-1, 0658 error('Cannot create file %s for writing. According to fopen.m: %s.', ... 0659 p.Results.file,msg); 0660 else 0661 %Write header column of times 0662 % Leave first column blank for channel labels 0663 if mean_wind, 0664 for t=1:n_wind 0665 fprintf(fid,' %d-%d',GRP.time_pts(use_tpts{t}(1)), ... 0666 GRP.time_pts(use_tpts{t}(end))); 0667 end 0668 0669 %write a couple spaces and then write header for t-scores 0670 fprintf(fid,' '); 0671 for t=1:n_wind 0672 fprintf(fid,' %d-%d',GRP.time_pts(use_tpts{t}(1)), ... 0673 GRP.time_pts(use_tpts{t}(end))); 0674 end 0675 else 0676 for t=use_tpts, 0677 fprintf(fid,' %d',GRP.time_pts(t)); 0678 end 0679 end 0680 fprintf(fid,' Milliseconds\n'); 0681 0682 % Write channel labels and p-values 0683 chan_ct=0; 0684 for c=use_chans, 0685 chan_ct=chan_ct+1; 0686 fprintf(fid,'%s',GRP.chanlocs(c).labels); 0687 for t=1:length(use_tpts), 0688 fprintf(fid,' %f',prm_pval(chan_ct,t)); 0689 end 0690 fprintf(fid,' p-value'); 0691 0692 if mean_wind, 0693 %write a couple spaces and then write t-scores if mean amp 0694 %in time windows used 0695 fprintf(fid,' '); 0696 for t=1:n_wind 0697 fprintf(fid,' %f',data_t(chan_ct,t)); 0698 end 0699 fprintf(fid,' t-score \n'); 0700 else 0701 fprintf(fid,'\n'); 0702 end 0703 end 0704 0705 % Write permutation test details 0706 fprintf(fid,'Experiment: %s\n',GRP.exp_desc); 0707 fprintf(fid,'Test_of_null_hypothesis_that_Bin_%d_equals: %f\n',bin,p.Results.null_mean); 0708 fprintf(fid,'#_of_time_windows: %d\n',n_wind); 0709 fprintf(fid,'#_of_permutations: %d\n',p.Results.n_perm); 0710 fprintf(fid,'Tail_of_test: '); 0711 if ~p.Results.tail, 0712 fprintf(fid,'Two_tailed\n'); 0713 elseif p.Results.tail>0 0714 fprintf(fid,'Upper_tailed\n'); 0715 else 0716 fprintf(fid,'Lower_tailed\n'); 0717 end 0718 fprintf(fid,'Degrees_of_freedom: %d\n',df); 0719 fprintf(fid,'Alpha_level: %f\n',p.Results.alpha); 0720 fprintf(fid,'Cluster_inclusion_p_value_threshold: %f\n',p.Results.thresh_p); 0721 if isscalar(p.Results.chan_hood) 0722 fprintf(fid,'Max_spatial_neighbor_distance: %f\n',p.Results.chan_hood); 0723 end 0724 0725 for grp=[grpA grpB], 0726 % # of participants and filenames 0727 if grp==grpA, 0728 use_subs=use_subsA; 0729 fprintf(fid,'GND_fname_GroupA: %s\n',GRP.GND_fnames{grp}); 0730 fprintf(fid,'#_of_participants_GroupA: %d\n',n_subA); 0731 fprintf(fid,'Participant_names_GroupA: \n'); 0732 else 0733 use_subs=use_subsB; 0734 fprintf(fid,'GND_fname_GroupB: %s\n',GRP.GND_fnames{grp}); 0735 fprintf(fid,'#_of_participants_GroupB: %d\n',n_subB); 0736 fprintf(fid,'Participant_names_GroupB: \n'); 0737 end 0738 for s=1:length(use_subs), 0739 fprintf(fid,'%s\n',GRP.indiv_subnames{grp}{use_subs(s)}); 0740 end 0741 end 0742 end 0743 fclose(fid); 0744 end 0745 0746 if ~strcmpi(p.Results.save_GRP,'no'), 0747 GRP=save_matmk(GRP); 0748 end 0749 0750 % 0751 %% %%%%%%%%%%%%%%%%%%%%% function str2bool() %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 0752 % 0753 function bool=str2bool(str) 0754 %function bool=str2bool(str) 0755 0756 if ischar(str), 0757 if strcmpi(str,'yes') || strcmpi(str,'y') 0758 bool=1; 0759 else 0760 bool=0; 0761 end 0762 else 0763 bool=str; 0764 end 0765