├── .gitignore ├── FMUT_release_notes.md ├── F_sig_raster.m ├── FclustGND.m ├── FclustGRP.m ├── Fclust_corr.m ├── FfdrGND.m ├── FfdrGRP.m ├── FmaxGND.m ├── FmaxGRP.m ├── Fmax_corr.m ├── Ftest2xls.m ├── LICENSE.txt ├── README.md ├── add_poi_path.m ├── calc_Fclust.m ├── calc_Fmax.m ├── calc_param_ANOVA.m ├── epsGG.m ├── epsGG_license.txt ├── estimate_epsilon.m ├── flt2GND.m ├── fmut.py ├── format_xls.m ├── get_effects.m ├── get_int_res.m ├── get_mean_amplitude.m ├── get_poi_paths.m ├── perm_crANOVA.m ├── perm_rbANOVA.m ├── perm_spANOVA.m ├── poi_library ├── apache_poi_library_LICENSE ├── dom4j-1.6.1.jar ├── poi-3.8-20120326.jar ├── poi-ooxml-3.8-20120326.jar ├── poi-ooxml-schemas-3.8-20120326.jar ├── stax-api-1.0.1.jar └── xmlbeans-2.3.0.jar ├── py_addpath.m ├── pyinstaller ├── fmut_pyinstaller.cmd └── py_fmut_environment.yml ├── reduce_data.m ├── report_results.m ├── testing ├── A_setupTest.m ├── B_FmaxGNDTest.m ├── C_FclustGNDTest.m ├── D_FfdrGNDTest.m ├── E_FmaxGRPTest.m ├── F_FclustGRPTest.m ├── G_FfdrGRPTest.m ├── H_VariousTest.m ├── I_ANOVATest.m ├── external_check.m ├── simulated_data_rm.m └── simulated_data_sp.m ├── ttest2xls.m ├── xlwrite.m └── xlwrite_license.txt /.gitignore: -------------------------------------------------------------------------------- 1 | #Files for git to ignore for FMUT 2 | 3 | ### Files to ignore ### 4 | py_fmut.spec 5 | 6 | ### File types to ignore ### 7 | *.ffs_db 8 | *.lnk 9 | *.asv 10 | *.mat 11 | *.docx 12 | *.ffs_lock 13 | *.exe 14 | *.dll 15 | *.DS_Store 16 | *.mat 17 | *.GND 18 | *.GRP 19 | 20 | ### Folders to ignore ### 21 | *__pycache__* 22 | dev/* 23 | py_fmut_mac/* 24 | py_fmut_win/* 25 | testing/data/* 26 | testing/outputs/* 27 | build/* 28 | dist/* 29 | -------------------------------------------------------------------------------- /FMUT_release_notes.md: -------------------------------------------------------------------------------- 1 | # 0.5.1 2 | 3 | ## Installation instructions 4 | 5 | 1. Download FMUT_0.5.1.zip. 6 | 2. Unzip it. 7 | 3. Put the folder of files someplace sensible (e.g., in your "MATLAB" directory). 8 | 4. [Add the folder](https://www.mathworks.com/help/matlab/matlab_env/add-remove-or-reorder-folders-on-the-search-path.html) to the set of "paths" MATLAB searches when trying to answer a function call. 9 | 5. For Mac and Linux users: Run `>> add_poi_path` at the MATLAB command line, then close and restart MATLAB. 10 | 11 | The FMUT documentation can be found at: https://github.com/ericcfields/FMUT/wiki 12 | 13 | ## Release Notes 14 | 15 | ### Bug fixes 16 | * Error is now produced for unequal sample sizes with split plot designs. 17 | 18 | # 0.5.0 19 | 20 | ## Installation instructions 21 | 22 | 1. Download FMUT_0.5.0.zip. 23 | 2. Unzip it. 24 | 3. Put the folder of files someplace sensible (e.g., in your "MATLAB" directory). 25 | 4. [Add the folder](https://www.mathworks.com/help/matlab/matlab_env/add-remove-or-reorder-folders-on-the-search-path.html) to the set of "paths" MATLAB searches when trying to answer a function call. 26 | 5. For Mac and Linux users: Run `>> add_poi_path` at the MATLAB command line, then close and restart MATLAB. 27 | 28 | The FMUT documentation can be found at: https://github.com/ericcfields/FMUT/wiki 29 | 30 | ## Release Notes 31 | 32 | ### New features 33 | * `FfdrGND` (and relevant sub-functions) can now include a sphericity correction as long as there are no more than two factors with more than two levels. 34 | 35 | ### Bug fixes 36 | * `get_mean_amplitude` now removes commas from group and bin names in csv output to avoid formatting issues 37 | * Updated Python code for spreadsheet formatting so that highlighting will reflect the alpha level of the test (rather than always assuming alpha=0.05). Due to problems with pyinstaller, the frozen versions have not been updated. 38 | * fixed bug in `fmut.py` due to changes in openpyxl's cell.column format 39 | * Changed deprecated remove_sheets method in fmut.py 40 | * An error is now generated when a chan_hood matrix is not symmetrical 41 | 42 | ### Refactoring 43 | (These changes are unlikely to affect most users, but could affect your code if you have called some sub-functions directly.) 44 | 45 | * Basic permutation ANOVA functions now output F\_obs explicitly rather than implicitly (as the first permutation in F\_dist) 46 | * Code applying the Fmax and cluster corrections to ANOVA output have now been moved to separate functions: `Fclust_corr` and `Fmax_corr`. This was primarily to allow for more efficient simulation work (i.e., calculate the permutation ANOVA once and then applying each correction to the output). 47 | 48 | 49 | # 0.4.0-beta 50 | 51 | ## Installation instructions 52 | 53 | 1. Download FMUT_0.4.0-beta.zip. 54 | 2. Unzip it. 55 | 3. Put the folder of files someplace sensible (e.g., in your "MATLAB" directory). 56 | 4. [Add the folder](https://www.mathworks.com/help/matlab/matlab_env/add-remove-or-reorder-folders-on-the-search-path.html) to the set of "paths" MATLAB searches when trying to answer a function call. 57 | 5. For Mac and Linux users: Run `>> add_poi_path` at the MATLAB command line, then close and restart MATLAB. 58 | 59 | The FMUT documentation can be found at: https://github.com/ericcfields/FMUT/wiki 60 | 61 | ## Release Notes 62 | 63 | There are no known backwards incompatible changes from the 0.3.4 release. It is recommended that all users upgrade. 64 | 65 | ### New features 66 | * New function `get_mean_amplitude`: Extract the mean amplitude for each condition across the time points/electrodes where a mass univariate test revealed a significant effect. This is useful for testing whether an ERP effect correlates with other measures (e.g., behavioral measures, individual differences). 67 | 68 | 69 | ### Bug fixes 70 | * Fixed formatting on cluster_id tab of spreadsheet output to handle more than 20 clusters 71 | * Updated Python code for spreadsheet formatting to account for deprecated openpyxl functions and and upcoming Python syntax changes for escape characters 72 | 73 | # 0.3.4-beta 74 | 75 | ## Installation instructions 76 | 77 | 1. Download FMUT_0.3.4-beta.zip. 78 | 2. Unzip it. 79 | 3. Put the folder of files someplace sensible (e.g., in your "MATLAB" directory). 80 | 4. [Add the folder](https://www.mathworks.com/help/matlab/matlab_env/add-remove-or-reorder-folders-on-the-search-path.html) to the set of "paths" MATLAB searches when trying to answer a function call. 81 | 5. For Mac and Linux users: run `>> add_poi_path` at the MATLAB command line. Close and restart MATLAB. 82 | 83 | The FMUT documentation can be found at: https://github.com/ericcfields/FMUT/wiki 84 | 85 | ## Release Notes 86 | 87 | ### Minor changes 88 | * Removed warning about three-factor designs. 89 | * Specifying a factor with one level now produces an informative error message. 90 | 91 | ### Bug fixes 92 | * Fixed a bug leading to an error message for black and white raster plots. 93 | 94 | # 0.3.3-beta 95 | 96 | ## Installation instructions 97 | 98 | 1. Download FMUT_0.3.3-beta.zip 99 | 2. Unzip it 100 | 3. Put the folder of files someplace sensible (e.g., in your "MATLAB" directory) 101 | 4. [Add the folder](https://www.mathworks.com/help/matlab/matlab_env/add-remove-or-reorder-folders-on-the-search-path.html) to the set of "paths" MATLAB searches when trying to answer a function call 102 | 5. **New to this release:** For Mac and Linux users, run `>> add_poi_path` at the MATLAB command line. Close and restart MATLAB. 103 | 104 | The FMUT documentation can be found at: https://github.com/ericcfields/FMUT/wiki 105 | 106 | ## Release Notes 107 | 108 | This is a bug fix release. There are no known backwards incompatible changes from the previous 0.3.x releases. It is recommended that all users upgrade. 109 | 110 | ### Bug fixes 111 | * Time points no longer display in scientific notation in spreadsheet output. 112 | * `ttest2xls` now reports correct number of subjects on the test_summary page for between subjects designs. Previously the number was one less than the true number of subjects. 113 | * Spreadsheet outputting no longer skips some clusters on the cluster summary page. 114 | * For Mac and Linux users the `Ftest2xls` and `ttest2xls` functions call MATLAB's `javaaddpath` function, which runs `clear all`. This can clear global variables and cause other problems. Two changes in this release help address this: 115 | * FMUT functions were updated to accommodate the behavior of `javaaddpath` without producing an error 116 | * A full solution is provided by the new function `add_poi_path`, which adds the POI libraries to the static path, avoiding the need for `javaaddpath`. See modification to installation instructions above. 117 | 118 | ### Minor and internal changes 119 | * Changes to pyinstaller packed versions of fmut.py (spreadsheet formatting): 120 | * Now built from Python 3.6 121 | * Now use one-folder option rather than one-file, which runs faster 122 | 123 | # 0.3.2-beta 124 | 125 | This is a bug fix release. There are no known backwards incompatible changes from the previous 0.3.x releases. It is recommended that all users upgrade. 126 | 127 | ### Minor changes 128 | * Updated function documentation 129 | * Updated `py_addpath` function to handle relative paths better 130 | * Can now use other corrections with FDR functions (undocumented) 131 | 132 | ### Bug fixes 133 | * When the time window input specifies a time outside the epoch, an informative error message is now produces. Previously the analyses was run on a shortened time window. 134 | 135 | # 0.3.1-beta 136 | 137 | ## Release Notes 138 | 139 | This is a bug fix release. It is recommended that all users upgrade. 140 | 141 | ### Minor changes 142 | * The beta warning displayed for the main functions has been made less severe, reflecting extensive additional testing of the software. 143 | * `perm_rbANOVA` and `perm_spANOVA` now have input option to determine whether data is reduced before analysis. This is an internal change intended for testing purposes. 144 | 145 | ### Bug fixes 146 | * Tests with between-subjects factors now report the correct number of subjects in spreadsheet output 147 | * Spreadsheet formatting now removes all blank sheets (rather than just Sheet1) 148 | 149 | # 0.3.0-beta 150 | 151 | ## Release notes 152 | 153 | ### Major changes 154 | * FMUT now supports ANOVA designs with a between subjects factor. This is implemented via the new functions `FmaxGRP`, `FclustGRP`, and `FfdrGRP` which take a Mass Univariate Toolbox `GRP` variable as the input. 155 | 156 | ### Backwards compatibility 157 | * Two new fields, `use_groups` and `group_n`, have been added to the `F_tests` struct. Attempts to add results to a GND with results missing this field will result in an error. 158 | * Inputs to some subfunctions have changed: `calc_Fclust`, `calc_Fmax`, and `calc_param_ANOVA` now take `cond_subs` as the second argument to specify the between subjects structure. `reduce_data` and `get_int_res` now require `cond_subs` and/or `dims` inputs. 159 | 160 | 161 | # 0.2.0-beta 162 | 163 | ## Release notes 164 | 165 | ### Major changes 166 | * The `'int_method'` input to permutation-based functions has been eliminated. It was ambiguous what this input meant for more complex designs with multiple interactions where an exact test is possible for some of the effects and not others and for effects where a combination of data reduction, restricted permutation, and/or permutation residuals is ideal. All effects are now reduced to the maximum extent possible. See the [FMUT Documentation](https://github.com/ericcfields/FMUT/wiki/Mass-univariate-statistics-and-corrections#how-fmut-calculates-effects-in-factorial-designs) for more details. 167 | * Related changes in the code base mean that FMUT functions can now handle a wider array of designs beyond three-way designs. 168 | 169 | ### Backwards compatibility 170 | * Function calls with the `'int_method'` input will now result in an error 171 | * The structure of the `F_tests` field of the `GND` now has a new field, `exact_test`, which contains a Boolean for each effect indicating whether it was an exact test. This change in the structure will generate an error if you try to add new results to a `GND` with results in the old format. 172 | * The `test_results` struct returned by several sub-functions also now has an `exact_test` field 173 | 174 | 175 | 176 | # 0.1.1-beta 177 | 178 | ## Release notes 179 | 180 | This is a bug fix release. It is recommended that all users upgrade. 181 | 182 | ### Bug fixes 183 | * flt2GND can now handle spaces in AVGDUMP filename and path or flt filename 184 | * flt2GND now saves correctly when ‘yes’ is specified for the ‘save_GND’ input 185 | * Ftest2xls now correctly handles long effect names 186 | 187 | # 0.1.0-beta 188 | 189 | 190 | ## Release notes 191 | 192 | First beta release. -------------------------------------------------------------------------------- /Fclust_corr.m: -------------------------------------------------------------------------------- 1 | %Calculate cluster correction for a F-observed and the permutation 2 | %distribution 3 | % 4 | %EXAMPLE USAGE 5 | % >> [h, p, clust_info, est_alpha] = Fclust_corr(F_obs, F_dist, 0.05, chan_hood, 3.84) 6 | % 7 | %REQUIRED INPUTS 8 | % F_obs - An electrode x time points array of observed F-values 9 | % F-dist - A permutation x electrode x time point array of the 10 | % permutation F-distribution 11 | % alpha - Alpha level to use for hypothesis test 12 | % chan_hood - A symmetric 2d matrix indicating which channels are 13 | % neighbors with other channels. If chan_hood(a,b)=1, 14 | % then Channel A and B are neighbors. This is produced by the 15 | % function spatial_neighbors.m. 16 | % thresh_F - F-statistic threshold for inclusion in cluster 17 | % 18 | %OUTPUT 19 | % h - electrode x time point array indicating which locations 20 | % are part of a statistically significant cluster 21 | % p - electrode x time point array of p-values 22 | % clust_info - struct containing information about each cluster 23 | % est_alpha - estimated achieved alpha level of the test; may not be 24 | % accurate if F_dist was generated by an approximate 25 | % permutation method 26 | % 27 | % 28 | %VERSION DATE: 4 March 2020 29 | %AUTHOR: Eric Fields 30 | % 31 | %NOTE: This function is provided "as is" and any express or implied warranties 32 | %are disclaimed. 33 | 34 | %Copyright (c) 2017-2019, Eric Fields 35 | %All rights reserved. 36 | %This code is free and open source software made available under the 3-clause BSD license. 37 | 38 | function [h, p, clust_info, est_alpha] = Fclust_corr(F_obs, F_dist, alpha, chan_hood, thresh_F) 39 | 40 | global VERBLEVEL 41 | if VERBLEVEL 42 | fprintf('Calculating clusters . . . ') 43 | end 44 | 45 | if ~isequal(chan_hood, chan_hood') 46 | error('Your chan_hood matrix is not symmetric') 47 | end 48 | 49 | %Some useful numbers 50 | [n_perm, n_electrodes, n_time_pts] = size(F_dist); 51 | 52 | %Find clusters and cluster mass distribution 53 | clust_mass_dist = zeros(n_perm, 1); 54 | for i = 1:n_perm 55 | F = reshape(F_dist(i, :, :), [n_electrodes, n_time_pts]); 56 | %Find clusters in F 57 | [clust_ids, n_clust] = find_clusters(F, thresh_F, chan_hood, 1); 58 | %Save observed data 59 | if i == 1 60 | clust_ids_obs = clust_ids; 61 | n_clust_obs = n_clust; 62 | end 63 | %Find largest cluster mass for this permutation 64 | for c = 1:n_clust 65 | use_mass = sum(F(clust_ids == c)); 66 | if use_mass > clust_mass_dist(i) 67 | clust_mass_dist(i) = use_mass; 68 | end 69 | end 70 | end 71 | 72 | %Get cluster mass critical value 73 | clust_mass_dist = sort(clust_mass_dist); 74 | clust_mass_crit = clust_mass_dist(ceil((1-alpha) * n_perm)); 75 | 76 | %Find cluster mass for each observed cluster and compare to critical 77 | %value 78 | clust_mass_obs = NaN(1, n_clust_obs); 79 | clust_pval = NaN(1, n_clust_obs); 80 | null_test = false(1, n_clust_obs); 81 | p = ones(n_electrodes, n_time_pts); 82 | for c = 1:n_clust_obs 83 | clust_mass_obs(c) = sum(F_obs(clust_ids_obs == c)); 84 | if clust_mass_obs(c) > clust_mass_crit 85 | null_test(c) = true; 86 | end 87 | clust_pval(c) = mean(clust_mass_dist >= clust_mass_obs(c)); 88 | p(clust_ids_obs == c) = clust_pval(c); 89 | end 90 | h = (p <= alpha); 91 | est_alpha = mean(clust_mass_dist>clust_mass_crit); 92 | if VERBLEVEL 93 | fprintf('DONE\n'); 94 | fprintf('Estimated alpha level is %f\n', est_alpha); 95 | end 96 | 97 | clust_info = struct('null_test', null_test, ... 98 | 'pval', clust_pval, ... 99 | 'clust_mass', clust_mass_obs, ... 100 | 'clust_ids', clust_ids_obs); 101 | 102 | end 103 | -------------------------------------------------------------------------------- /Fmax_corr.m: -------------------------------------------------------------------------------- 1 | %Calculate Fmax correction for a F-observed and the permutation 2 | %distribution 3 | % 4 | %EXAMPLE USAGE 5 | % >> [h, p, Fmax_crit, est_alpha] = Fmax_corr(F_obs, F_dist, 0.05) 6 | % 7 | %REQUIRED INPUTS 8 | % F_obs - An electrode x time points array of observed F-values 9 | % F-dist - A permutation x electrode x time point array of the 10 | % permutation F-distribution 11 | % alpha - Alpha level to use for hypothesis test 12 | % 13 | %OUTPUT 14 | % h - electrode x time point array indicating which locations 15 | % are part of a statistically significant cluster 16 | % p - electrode x time point array of p-values 17 | % Fmax_crit - Critical value for statistical significance 18 | % est_alpha - estimated achieved alpha level of the test; may not be 19 | % accurate if F_dist was generated by an approximate 20 | % permutation method 21 | % 22 | % 23 | %VERSION DATE: 4 April 2019 24 | %AUTHOR: Eric Fields 25 | % 26 | %NOTE: This function is provided "as is" and any express or implied warranties 27 | %are disclaimed. 28 | 29 | %Copyright (c) 2017-2019, Eric Fields 30 | %All rights reserved. 31 | %This code is free and open source software made available under the 3-clause BSD license. 32 | 33 | function [h, p, Fmax_crit, est_alpha] = Fmax_corr(F_obs, F_dist, alpha) 34 | 35 | global VERBLEVEL; 36 | 37 | %Some useful numbers 38 | [~, n_electrodes, n_time_pts] = size(F_dist); 39 | 40 | %Calculate Fmax distribution and Fmax critical value 41 | Fmax_dist = max(max(F_dist, [], 2), [], 3); 42 | Fmax_dist = sort(Fmax_dist); 43 | Fmax_crit = Fmax_dist(ceil((1-alpha) * length(Fmax_dist))); 44 | 45 | %Null hypothesis test 46 | h = F_obs > Fmax_crit; 47 | est_alpha = mean(Fmax_dist > Fmax_crit); 48 | if VERBLEVEL 49 | fprintf('Estimated alpha level is %f\n', est_alpha); 50 | end 51 | 52 | %Calculate p-value 53 | p = NaN(n_electrodes, n_time_pts); 54 | for e = 1:n_electrodes 55 | for t = 1:n_time_pts 56 | p(e,t) = mean(Fmax_dist >= F_obs(e,t)); 57 | end 58 | end 59 | 60 | assert(isequal(h, p<=alpha)); 61 | 62 | end 63 | -------------------------------------------------------------------------------- /Ftest2xls.m: -------------------------------------------------------------------------------- 1 | %Output results of F-test to a spreadsheet. 2 | % 3 | %EXAMPLE USAGE 4 | % >> Ftest2xls(GND, 1, 'example.xlsx') 5 | % 6 | %REQUIRED INPUTS 7 | % GND - A GND or GRP variable with F-test results 8 | % test_id - The test number within the F_tests field of the GND 9 | % struct 10 | % output_fname - The filename for the spreadsheet that will be saved. If 11 | % you don't want to save in the current working directory, 12 | % include a full filepath 13 | %OPTIONAL INPUT 14 | % format_output - A boolean specifying whether to apply formatting to the 15 | % spreadsheet output. {default: true} 16 | % 17 | %VERSION DATE: 18 April 2019 18 | %AUTHOR: Eric Fields 19 | % 20 | %NOTE: This function is provided "as is" and any express or implied warranties 21 | %are disclaimed. 22 | 23 | %Copyright (c) 2017, Eric Fields 24 | %All rights reserved. 25 | %This code is free and open source software made available under the 3-clause BSD license. 26 | 27 | 28 | function Ftest2xls(GND, test_id, output_fname, format_output) 29 | 30 | %% Set-up 31 | 32 | global VERBLEVEL; 33 | if isempty(VERBLEVEL) 34 | VERBLEVEL = 2; 35 | end 36 | 37 | %Set formatting option if no input 38 | if nargin < 4 %#ok 39 | if ispc() 40 | format_output = true; 41 | else 42 | format_output = false; 43 | end 44 | end 45 | if format_output && ~ispc() 46 | watchit(sprintf('Spreadsheet formatting on non-Windows systems is buggy.\nSee the FMUT documentation for an explanation and possible workaround.')) 47 | end 48 | 49 | %Define function for writing to spreadsheet and update Java class path 50 | %if necessary 51 | if ispc() 52 | writexls = @xlswrite; 53 | else 54 | %Get full path for POI .jar files not on static Java class path 55 | [~, missing_poi_files] = get_poi_paths(); 56 | %Add POI file to dynamic Java class path if they aren't on the 57 | %static path 58 | if ~isempty(missing_poi_files) 59 | if VERBLEVEL 60 | watchit(sprintf(['POI libraries are not on the static Java class path.\n', ... 61 | 'This can delete global variables and create other problems.\n', ... 62 | 'You can avoid this by running add_poi_path.'])); 63 | end 64 | %Add each .jar file to the dynamic path 65 | add_poi_path('-dynamic'); 66 | end 67 | writexls = @xlwrite; 68 | end 69 | 70 | %Make sure we're not adding sheets to existing file 71 | if exist(output_fname, 'file') 72 | user_resp = questdlg(sprintf('WARNING: %s already exists. Do you want to overwrite it?', output_fname), 'WARNING'); 73 | if strcmp(user_resp, 'No') 74 | return; 75 | else 76 | delete(output_fname) 77 | end 78 | end 79 | 80 | %Create some variables for easier reference 81 | results = GND.F_tests(test_id); 82 | [~, effects_labels] = get_effects(results.factors); 83 | n_subs = sum(results.group_n); 84 | 85 | warning('off', 'MATLAB:xlswrite:AddSheet') 86 | 87 | %% Summary sheet 88 | 89 | fact_levels = sprintf('%d X ', results.factor_levels); 90 | fact_levels = fact_levels(1:end-2); 91 | if iscell(results.use_groups) 92 | use_groups = [sprintf('%s, ', results.use_groups{1:end-1}), results.use_groups{end}]; 93 | elseif isnan(results.use_groups) 94 | use_groups = 'N/A (no between-subjects factor)'; 95 | else 96 | error('Cannot interpret F_tests.use_groups'); 97 | end 98 | summary = {'Study', GND.exp_desc; ... 99 | 'GND', [GND.filepath GND.filename]; ... 100 | 'Groups', use_groups; ... 101 | '# Subjects in group', [sprintf('%d ', results.group_n(1:end-1)), num2str(results.group_n(end))]; 102 | 'Bins', sprintf('%d ', results.bins); ... 103 | 'Factors', [sprintf('%s X ', results.factors{1:end-1}), results.factors{end}]; ... 104 | 'Factor_levels', fact_levels; ... 105 | 'Time Window', sprintf('%.0f - %.0f ', results.time_wind'); ... 106 | 'Mean window', results.mean_wind; ... 107 | 'Electrodes', [sprintf('%s, ', results.include_chans{1:end-1}), results.include_chans{end}]; ... 108 | 'Multiple comparisons correction method', results.mult_comp_method; ... 109 | 'Number of permutations', results.n_perm; ... 110 | 'Alpha or q(FDR)', results.desired_alphaORq; ... 111 | '# subjects', n_subs}; 112 | if ~strcmpi(results.mult_comp_method, 'cluster mass perm test') 113 | if ~isstruct(results.F_crit) 114 | summary(end+1, :) = {'F critical value', results.F_crit}; 115 | else 116 | for i = 1:length(effects_labels) 117 | summary(end+1, :) = {sprintf('%s F critical value', effects_labels{i}), ... 118 | results.F_crit.(effects_labels{i})}; %#ok 119 | end 120 | end 121 | end 122 | writexls(output_fname, summary, 'test summary'); 123 | 124 | %% Cluster summary sheet 125 | 126 | if strcmpi(results.mult_comp_method, 'cluster mass perm test') 127 | clust_sum = cell(11, 3*length(effects_labels)-1); 128 | col = 1; 129 | for i = 1:length(effects_labels) 130 | effect = effects_labels{i}; 131 | clust_sum{1, col} = effect; 132 | row = 3; 133 | if length(effects_labels) == 1 134 | num_clusters = length(results.clust_info.pval); 135 | else 136 | num_clusters = length(results.clust_info.(effect).pval); 137 | end 138 | for cluster = 1:num_clusters 139 | %labels 140 | clust_sum(row:row+8, col) = {sprintf('CLUSTER %d', cluster); ... 141 | 'cluster mass'; 'p-value'; 'spatial extent'; ... 142 | 'temporal extent'; 'spatial peak'; 'temporal peak'; ... 143 | 'spatial mass peak'; 'temporal mass peak'}; 144 | %assign cluster mass and p-value; 145 | %get array of F-values in cluster 146 | if length(effects_labels) == 1 147 | clust_sum{row+1, col+1} = results.clust_info.clust_mass(cluster); 148 | clust_sum{row+2, col+1} = results.clust_info.pval(cluster); 149 | clust_Fobs = results.F_obs; 150 | clust_Fobs(results.clust_info.clust_ids ~= cluster) = 0; 151 | else 152 | clust_sum{row+1, col+1} = results.clust_info.(effect).clust_mass(cluster); 153 | clust_sum{row+2, col+1} = results.clust_info.(effect).pval(cluster); 154 | clust_Fobs = results.F_obs.(effect); 155 | clust_Fobs(results.clust_info.(effect).clust_ids ~= cluster) = 0; 156 | end 157 | %spatial extent 158 | clust_sum{row+3, col+1} = sprintf('%s, ', results.include_chans{any(clust_Fobs, 2)}); 159 | %temporal extent 160 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 161 | clust_sum{row+4, col+1} = sprintf('Mean window: %.0f - %.0f', results.time_wind(1), results.time_wind(2)); 162 | else 163 | clust_sum{row+4, col+1} = sprintf('%.0f - %.0f', ... 164 | GND.time_pts(min(results.used_tpt_ids(any(clust_Fobs, 1)))), ... 165 | GND.time_pts(max(results.used_tpt_ids(any(clust_Fobs, 1))))); 166 | end 167 | %Spatial and temporal peak 168 | [max_elec, max_timept] = find(clust_Fobs == max(clust_Fobs(:))); %find location of max F in cluster 169 | clust_sum{row+5, col+1} = results.include_chans{max_elec}; 170 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 171 | clust_sum{row+6, col+1} = sprintf('Mean window: %.0f - %.0f', results.time_wind(1), results.time_wind(2)); 172 | else 173 | clust_sum{row+6, col+1} = sprintf('%.0f', GND.time_pts(results.used_tpt_ids(max_timept))); 174 | end 175 | %Spatial and temporal center (collapsed across the other 176 | %dimension) 177 | [~, max_elec_clust] = max(sum(clust_Fobs, 2)); 178 | clust_sum{row+7, col+1} = results.include_chans{max_elec_clust}; 179 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 180 | clust_sum{row+8, col+1} = sprintf('Mean window: %.0f - %.0f', results.time_wind(1), results.time_wind(2)); 181 | else 182 | [~, max_time_clust] = max(sum(clust_Fobs, 1)); 183 | clust_sum{row+8, col+1} = sprintf('%0.f', GND.time_pts(results.used_tpt_ids(max_time_clust))); 184 | end 185 | row = row+10; 186 | end 187 | col = col+3; 188 | end 189 | writexls(output_fname, clust_sum, 'cluster summary'); 190 | end 191 | 192 | %% Cluster IDs, F obs, p-values for each effect 193 | 194 | %Create headers 195 | chan_header = [' '; results.include_chans']; 196 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 197 | time_header = cell(1, size(results.time_wind, 1)); 198 | for t = 1:size(results.time_wind, 1) 199 | time_header{t} = sprintf('%.0f - %.0f', results.time_wind(t,1), results.time_wind(t,2)); 200 | end 201 | else 202 | time_header = num2cell(GND.time_pts(results.used_tpt_ids)); 203 | end 204 | 205 | %Write sheets 206 | if length(effects_labels) == 1 207 | 208 | %Sheet names are limited to 31 characters 209 | if length(effects_labels{1}) > 19 210 | sheet_label = effects_labels{1}(1:19); 211 | else 212 | sheet_label = effects_labels{1}; 213 | end 214 | 215 | %Cluster_ids 216 | if strcmpi(results.mult_comp_method, 'cluster mass perm test') 217 | %cluster ids 218 | clust_ids = [chan_header, [time_header; num2cell(results.clust_info.clust_ids)]]; 219 | writexls(output_fname, clust_ids, sprintf('%s_clust_IDs', sheet_label)); 220 | end 221 | 222 | %F_obs 223 | F_obs_table = [chan_header, [time_header; num2cell(results.F_obs)]]; 224 | writexls(output_fname, F_obs_table, sprintf('%s_F_obs', sheet_label)); 225 | 226 | %p-values 227 | if ~strcmpi(results.mult_comp_method, 'bky') 228 | adj_pvals = [chan_header, [time_header; num2cell(results.adj_pval)]]; 229 | writexls(output_fname, adj_pvals, sprintf('%s_adj_pvals', sheet_label)); 230 | end 231 | 232 | else 233 | for i = 1:length(effects_labels) 234 | 235 | %Sheet names are limited to 31 characters 236 | if length(effects_labels{i}) > 19 237 | sheet_label = effects_labels{i}(1:19); 238 | else 239 | sheet_label = effects_labels{i}; 240 | end 241 | 242 | %Cluster_ids 243 | if strcmpi(results.mult_comp_method, 'cluster mass perm test') 244 | clust_ids = [chan_header, [time_header; num2cell(results.clust_info.(effects_labels{i}).clust_ids)]]; 245 | writexls(output_fname, clust_ids, sprintf('%s_clust_IDs', sheet_label)); 246 | end 247 | 248 | %F_obs 249 | F_obs_table = [chan_header, [time_header; num2cell(results.F_obs.(effects_labels{i}))]]; 250 | writexls(output_fname, F_obs_table, sprintf('%s_F_obs', sheet_label)); 251 | 252 | %p-values 253 | if ~strcmpi(results.mult_comp_method, 'bky') 254 | adj_pvals = [chan_header, [time_header; num2cell(results.adj_pval.(effects_labels{i}))]]; 255 | writexls(output_fname, adj_pvals, sprintf('%s_adj_pvals', sheet_label)); 256 | end 257 | 258 | end 259 | end 260 | 261 | %Try to format 262 | if format_output 263 | try 264 | format_xls(output_fname) 265 | catch ME 266 | watchit(sprintf('Could not format spreadsheet because of the following error:\n%s\n', ME.message)) 267 | end 268 | end 269 | 270 | end 271 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Factorial Mass Univariate ERP toolbox 2 | 3 | Copyright (c) 2017-2019, Eric C. Fields 4 | All rights reserved 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | 16 | ************************************************************** 17 | 18 | The Factorial Extension to the Mass Univariate ERP toolbox reuses some code from the Mass Univariate Toolbox (MUT) by David Groppe. The MUT license is reproduced below. 19 | 20 | Copyright (c) 2015, David Groppe 21 | All rights reserved. 22 | 23 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 24 | 25 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 26 | 27 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 28 | 29 | * Neither the name of Mass_Univariate_ERP_Toolbox nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 30 | 31 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Factorial Mass Univariate ERP Toolbox (FMUT) 2 | 3 | **VERSION** 4 | 0.5.1 5 | 6 | **AUTHOR** 7 | Eric Fields 8 | 9 | **CONTACT** 10 | fieldsec@westminster.edu 11 | 12 | The Factorial Mass Univariate ERP Toolbox (FMUT) is an extension to David Groppe's Mass Univariate ERP Toolbox. The FMUT implements permutation and false discovery rate based mass univariate statistics for ANOVA and factorial designs for event-related potential (ERP) data. 13 | 14 | Download and installation instructions for FMUT: 15 | https://github.com/ericcfields/FMUT/releases 16 | 17 | The FMUT documentation is available at: 18 | https://github.com/ericcfields/FMUT/wiki 19 | 20 | For information on the Mass Univariate Toolbox, see: 21 | http://www.openwetware.org/wiki/Mass_Univariate_ERP_Toolbox 22 | -------------------------------------------------------------------------------- /add_poi_path.m: -------------------------------------------------------------------------------- 1 | %Add Apache POI library to MATLAB's static Java class path 2 | % 3 | %EXAMPLE USAGE 4 | % >> add_poi_path 5 | % 6 | %INPUT 7 | % segment - if '-static', add POI library .jar files to the static path; if 8 | % '-dynamic', add to the dynamic path. {default: '-static'} 9 | % 10 | %VERSION DATE: 20 November 2017 11 | %AUTHOR: Eric Fields 12 | % 13 | %NOTE: This function is provided "as is" and any express or implied warranties 14 | %are disclaimed. 15 | 16 | %Copyright (c) 2017, Eric Fields 17 | %All rights reserved. 18 | %This code is free and open source software made available under the 3-clause BSD license. 19 | 20 | function add_poi_path(segment) 21 | 22 | %Default to updating static path 23 | if ~nargin 24 | segment = '-static'; 25 | end 26 | 27 | %Get full path for POI .jar files not on static Java class path 28 | [~, missing_poi_files] = get_poi_paths(); 29 | 30 | %Nothing to do if all files are on static path 31 | if isempty(missing_poi_files) 32 | return; 33 | end 34 | 35 | if strcmpi(segment, '-static') 36 | 37 | %Full path for javaclasspath file 38 | static_file = [prefdir filesep 'javaclasspath.txt']; 39 | %Append missing POI files to javaclasspath.txt file 40 | f_out = fopen(static_file, 'a'); 41 | for i = 1:length(missing_poi_files) 42 | fprintf(f_out, '%s\n', missing_poi_files{i}); 43 | end 44 | fclose(f_out); 45 | 46 | %POI files will not actually be added to the Java class path until 47 | %MATLAB is restarted 48 | msgbox('Close and restart MATLAB to finish adding the Apache POI library to the Java class path.') 49 | 50 | elseif strcmpi(segment, '-dynamic') 51 | 52 | %Add any file no on the static or dynamic path to the dynamic path 53 | for i = 1:length(missing_poi_files) 54 | jar_file = missing_poi_files{i}; 55 | if ~ismember(jar_file, javaclasspath('-dynamic')) 56 | javaaddpath(jar_file); 57 | end 58 | end 59 | 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /calc_Fclust.m: -------------------------------------------------------------------------------- 1 | %Calculate F-test cluster mass permutation effect. 2 | % 3 | %EXAMPLE USAGE 4 | % >> test_results = calc_Fclust(data, [], [3, 4], 1e4, 0.05, chan_hood, 0.05) 5 | % 6 | %REQUIRED INPUTS 7 | % data - An electrode x time points x conditions x subjects array of ERP 8 | % data. Array will vary in number of dimensions based on how many 9 | % factors there are 10 | % cond_subs - Array giving the number of subjects in each condition of 11 | % the between subjects factor. For example, if cond_subs is 12 | % [8, 9], then there should be 17 subjects with the first 8 13 | % being in condition A and the next 9 being in condition B. 14 | % For fully within-subjects designs cond_subs = [] 15 | % dims - Dimensions of the data array involved in the effect to be 16 | % calculated. For example, if data is an electrode x time points 17 | % x Factor A x Factor B x subjects array and you want to 18 | % calculated the main effect of A, dims = 3. If you want to 19 | % calculate the AxB interaciton, dims = [3, 4]. 20 | % n_perm - Number of permutations to use to calculate the null distribution 21 | % alpha - Family-wise alpha level of the test 22 | % chan_hood - A 2D symmetric binary matrix that indicates 23 | % which channels are considered neighbors of other 24 | % channels. E.g., if chan_hood(2,10)==1, then Channel 2 25 | % and Channel 10 are nieghbors. You can produce a 26 | % chan_hood matrix using the function spatial_neighbors.m. 27 | % thresh_p - The test-wise p-value threshold for cluster inclusion. If 28 | % a channel/time-point has a F-value that corresponds to an 29 | % uncorrected p-value greater than thresh_p, it is assigned 30 | % a p-value of 1 and not considered for clustering. 31 | % 32 | %OUTPUT 33 | % test_results - A struct with results of the cluster mass test 34 | % 35 | % 36 | %VERSION DATE: 4 April 2019 37 | %AUTHOR: Eric Fields 38 | % 39 | %NOTE: This function is provided "as is" and any express or implied warranties 40 | %are disclaimed. 41 | 42 | %Copyright (c) 2017-2019, Eric Fields 43 | %All rights reserved. 44 | %This code is free and open source software made available under the 3-clause BSD license. 45 | 46 | function test_results = calc_Fclust(data, cond_subs, dims, n_perm, alpha, chan_hood, thresh_p) 47 | 48 | %%% Calculate ANOVA %%% 49 | 50 | if ~isempty(cond_subs) && ~isequal(cond_subs, 0) && length(cond_subs) > 1 51 | [F_obs, F_dist, df_effect, df_res, exact_test] = perm_spANOVA(data, cond_subs, dims, n_perm); 52 | else 53 | [F_obs, F_dist, df_effect, df_res, exact_test] = perm_rbANOVA(data, dims, n_perm); 54 | end 55 | 56 | %%% Calculate cluster correction %%% 57 | 58 | thresh_F = finv(1-thresh_p, df_effect, df_res); 59 | [h, p, clust_info, est_alpha] = Fclust_corr(F_obs, F_dist, alpha, chan_hood, thresh_F); 60 | 61 | %%% Output %%% 62 | 63 | test_results.h = h; 64 | test_results.p = p; 65 | test_results.F_obs = F_obs; 66 | test_results.df = [df_effect, df_res]; 67 | test_results.clust_info = clust_info; 68 | if exact_test 69 | test_results.estimated_alpha = est_alpha; 70 | test_results.exact_test = true; 71 | else 72 | test_results.estimated_alpha = NaN; 73 | test_results.exact_test = false; 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /calc_Fmax.m: -------------------------------------------------------------------------------- 1 | %Calculate Fmax corrected F-test. 2 | % 3 | %EXAMPLE USAGE 4 | % >> test_results = calc_Fmax(data, [], [3, 4], 1e4, 0.05) 5 | % 6 | %REQUIRED INPUTS 7 | % data - An electrode x time points x conditions x subjects array of ERP 8 | % data. Array will vary in number of dimensions based on how many 9 | % factors there are 10 | % cond_subs - Array giving the number of subjects in each condition of 11 | % the between subjects factor. For example, if cond_subs is 12 | % [8, 9], then there should be 17 subjects with the first 8 13 | % being in condition A and the next 9 being in condition B. 14 | % For fully within-subjects designs cond_subs = [] 15 | % dims - Dimensions of the data array involved in the effect to be 16 | % calculated. For example, if data is an electrode x time points 17 | % x Factor A x Factor B x subjects array and you want to 18 | % calculated the main effect of A, dims = 3. If you want to 19 | % calculate the AxB interaciton, dims = [3, 4]. 20 | % n_perm - Number of permutations to use to calculate the null distribution 21 | % alpha - Family-wise alpha level of the test 22 | % 23 | %OUTPUT 24 | % test_results - A struct with results of the Fmax test 25 | % 26 | % 27 | %VERSION DATE: 4 April 2019 28 | %AUTHOR: Eric Fields 29 | % 30 | %NOTE: This function is provided "as is" and any express or implied warranties 31 | %are disclaimed. 32 | 33 | %Copyright (c) 2017-2019, Eric Fields 34 | %All rights reserved. 35 | %This code is free and open source software made available under the 3-clause BSD license. 36 | 37 | function test_results = calc_Fmax(data, cond_subs, dims, n_perm, alpha) 38 | 39 | %%% Calculate ANOVA %%% 40 | 41 | if ~isempty(cond_subs) && ~isequal(cond_subs, 0) && length(cond_subs) > 1 42 | [F_obs, F_dist, df_effect, df_res, exact_test] = perm_spANOVA(data, cond_subs, dims, n_perm); 43 | else 44 | [F_obs, F_dist, df_effect, df_res, exact_test] = perm_rbANOVA(data, dims, n_perm); 45 | end 46 | 47 | %%% Calculate Fmax correction %%% 48 | 49 | [h, p, Fmax_crit, est_alpha] = Fmax_corr(F_obs, F_dist, alpha); 50 | 51 | %%% Output %%% 52 | 53 | test_results.h = h; 54 | test_results.p = p; 55 | test_results.F_obs = F_obs; 56 | test_results.Fmax_crit = Fmax_crit; 57 | test_results.df = [df_effect, df_res]; 58 | if exact_test 59 | test_results.estimated_alpha = est_alpha; 60 | test_results.exact_test = true; 61 | else 62 | test_results.estimated_alpha = NaN; 63 | test_results.exact_test = false; 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /calc_param_ANOVA.m: -------------------------------------------------------------------------------- 1 | %Calculate parametric ANOVA at each time point and electrode with various 2 | %multiple comparisons corrections available. 3 | % 4 | %EXAMPLE USAGE 5 | % >> test_results = calc_param_ANOVA(data, [], [3, 4], 0.05, 'bh') 6 | % 7 | %REQUIRED INPUTS 8 | % data - An electrode x time points x conditions x subjects array of ERP 9 | % data. Array will vary in number of dimensions based on how many 10 | % factors there are. 11 | % cond_subs - Array giving the number of subjects in each condition of 12 | % the between subjects factor. For example, if cond_subs is 13 | % [8, 9], then there should be 17 subjects with the first 8 14 | % being in condition A and the next 9 being in condition B. 15 | % For fully within-subjects designs cond_subs = [] 16 | % dims - Dimensions of the data array involved in the effect to be 17 | % calculated. For example, if data is an electrode x time points 18 | % x Factor A x Factor B x subjects array and you want to 19 | % calculated the main effect of A, dims = 3. If you want to 20 | % calculate the AxB interaciton, dims = [3, 4]. 21 | % alphaORq - Test-wise alpha level (no correction), family-wise alpha 22 | % level (bonferroni or sidak correction) or False Discovery 23 | % Rate (bh, bk, or bky corrections) 24 | % 25 | %OPTIONAL INPUTS 26 | % correction - A string indicating a multiple comparisons correction. 27 | % Options are 'none', 'bonferroni', 'sidak', 'bh' (classic 28 | % Benjamini & Hochberg 1995) procedure, 'by' (Benjamini & 29 | % Yekutieli, 2001), or 'bky' (Benjamini, Krieger, & 30 | % Yekutieli, 2006). {default: 'none'}. 31 | % sphericity_corr - Indicates whether to apply a sphericity correction 32 | % with options of 'none', Greenhouse-Geisser ('gg'), 33 | % Hyunh-Feldt('hf'), or lower bound ('lb') 34 | % {default: 'none'} 35 | % 36 | %OUTPUT 37 | % test_results - A struct with results of the mass univariate ANOVA 38 | % 39 | % 40 | %VERSION DATE: 9 June 2020 41 | %AUTHOR: Eric Fields 42 | % 43 | %NOTE: This function is provided "as is" and any express or implied warranties 44 | %are disclaimed. 45 | 46 | %Copyright (c) 2020, Eric Fields 47 | %All rights reserved. 48 | %This code is free and open source software made available under the 3-clause BSD license. 49 | 50 | function test_results = calc_param_ANOVA(data, cond_subs, dims, alphaORq, correction, sphericity_corr) 51 | 52 | if nargin < 6 53 | sphericity_corr = 'none'; 54 | elseif ~any(strcmpi(sphericity_corr, {'none', 'gg', 'hf', 'lb'})) 55 | error('sphericity_corr must be ''none'', ''gg'', ''hf'', ''lb'''); 56 | end 57 | if nargin < 5 58 | correction = 'none'; 59 | elseif ~any(strcmpi(correction, {'none', 'bonferroni', 'sidak', 'bh', 'by', 'bky'})) 60 | error('correction must be ''none'', ''bonferroni'', ''sidak'', ''bh'', ''by'', or ''bky'''); 61 | end 62 | 63 | %Some useful numbers 64 | n_electrodes = size(data, 1); 65 | n_time_pts = size(data, 2); 66 | 67 | %% Calculate ANOVA 68 | 69 | %Calculate the ANOVA (F-obs and the permutation distribution) 70 | if ~isempty(cond_subs) && ~isequal(cond_subs, 0) && length(cond_subs) > 1 71 | [F_obs, ~, df_effect, df_res] = perm_spANOVA(data, cond_subs, dims, 1); 72 | else 73 | [F_obs, ~, df_effect, df_res] = perm_rbANOVA(data, dims, 1); 74 | end 75 | 76 | %Greenhouse-Geisser correction 77 | if ~strcmpi(sphericity_corr, 'none') 78 | epsilon = estimate_epsilon(data, cond_subs, dims, sphericity_corr); 79 | df_effect = df_effect*epsilon; 80 | df_res = df_res*epsilon; 81 | end 82 | 83 | %Get p-values 84 | uncorr_p = 1 - fcdf(F_obs, df_effect, df_res); 85 | 86 | 87 | %% Calculate F-crit, p-values, and null test with appropriate correction 88 | 89 | switch correction 90 | case 'none' 91 | F_crit = finv(1-alphaORq, df_effect, df_res); 92 | p = uncorr_p; 93 | h = F_obs > F_crit; 94 | case 'bonferroni' 95 | F_crit = finv(1 - (alphaORq/(n_electrodes * n_time_pts)), ... 96 | df_effect, df_res); 97 | p = uncorr_p * (n_electrodes * n_time_pts); 98 | p(p>1) = 1; 99 | h = F_obs > F_crit; 100 | case 'sidak' 101 | F_crit = finv((1-alphaORq).^(1/(n_electrodes*n_time_pts)), ... 102 | df_effect, df_res); 103 | p = 1 - (1 - uncorr_p) .^ (n_electrodes * n_time_pts); 104 | h = F_obs > F_crit; 105 | case 'bh' 106 | [h, crit_p, ~, p] = fdr_bh(uncorr_p, alphaORq, 'pdep', 'no'); 107 | case 'by' 108 | [h, crit_p, ~, p] = fdr_bh(uncorr_p, alphaORq, 'dep', 'no'); 109 | case 'bky' 110 | [h, crit_p] = fdr_bky(uncorr_p, alphaORq, 'no'); 111 | p = NaN; 112 | end 113 | if any(strcmpi(correction, {'bh', 'by', 'bky'})) 114 | if crit_p == 0 115 | F_crit = NaN; 116 | else 117 | F_crit = finv(1-crit_p, df_effect, df_res); 118 | end 119 | end 120 | if ~strcmpi(correction ,'bky') 121 | assert(isequal(h, p<=alphaORq)); 122 | end 123 | 124 | %Create results struct 125 | test_results.h = h; 126 | test_results.p = p; 127 | test_results.F_obs = F_obs; 128 | test_results.F_crit = F_crit; 129 | test_results.df = [df_effect, df_res]; 130 | 131 | end 132 | -------------------------------------------------------------------------------- /epsGG.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericcfields/FMUT/f93e56a3ca3326248b281e9803152b185c809d21/epsGG.m -------------------------------------------------------------------------------- /epsGG_license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009, Antonio Trujillo-Ortiz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /estimate_epsilon.m: -------------------------------------------------------------------------------- 1 | %Calculate the Greenhouse-Geisser, Hyunh-Feldt, or lower bound estimate 2 | %of epsilon at all time points and electrodes 3 | % 4 | %EXAMPLE USAGE 5 | % >> epsilon = estimate_epsilon(data, [], 3, 'gg'); 6 | % 7 | %REQUIRED INPUTS 8 | % data - An electrode x time points x conditions x subjects array 9 | % of ERP data. Array will vary in number of dimensions based 10 | % on how many factors there are. 11 | % cond_subs - Array giving the number of subjects in each condition of 12 | % the between subjects factor. For example, if cond_subs is 13 | % [8, 9], then there should be 17 subjects with the first 8 14 | % being in condition A and the next 9 being in condition B. 15 | % For fully within-subjects designs cond_subs = [] 16 | % dims - Dimensions of the data array involved in the effect to be 17 | % calculated. For example, if data is an electrode x time 18 | % points x Factor A x Factor B x subjects array and you want to 19 | % calculate the main effect of A, dims = 3. If you want to 20 | % calculate the AxB interaciton, dims = [3, 4]. 21 | % method - 'gg': Greenhouse-Geisser, 'hf':Hyunh-Feldt, or 'lb': 22 | % lower bound {default: 'gg'} 23 | % 24 | %OUTPUT 25 | % epsilon - electrode x time point array of epsilon estimate 26 | % 27 | % 28 | %VERSION DATE: 13 June 2020 29 | %AUTHOR: Eric Fields 30 | % 31 | %NOTE: This function is provided "as is" and any express or implied warranties 32 | %are disclaimed. 33 | 34 | %Copyright (c) 2020, Eric Fields 35 | %All rights reserved. 36 | %This code is free and open source software made available under the 3-clause BSD license. 37 | 38 | function epsilon = estimate_epsilon(data, cond_subs, dims, method) 39 | 40 | %Default to Greenhouse-Geisser method 41 | if nargin < 4 42 | method = 'gg'; 43 | end 44 | 45 | %Check input 46 | if ~any(strcmpi(method, {'gg', 'hf', 'lb'})) 47 | error('method input must be ''gg'', ''hf'' or ''lb'''); 48 | end 49 | 50 | %Get data reduced for analysis 51 | reduced_data = reduce_data(data, dims); 52 | 53 | %This function currently only works for one-way fully repeated measures designs 54 | if ~isempty(cond_subs) && ~isequal(cond_subs, 0) && length(cond_subs) > 1 55 | error('estimate_epsilon curently only implemented for fully repeated measures ANOVA'); 56 | end 57 | if ndims(reduced_data) ~= 4 58 | error('estimate_epsilon currently does not support more than one factor with more than 2 levels'); 59 | end 60 | 61 | %Get some useful numbers 62 | [n_electrodes, n_time_pts, n_conds, n_subs] = size(reduced_data); 63 | df_effect = n_conds - 1; 64 | 65 | if strcmpi(method, 'lb') 66 | 67 | %Lower bound on epsilon 68 | epsilon = (1/df_effect) * ones(n_electrodes, n_time_pts); 69 | 70 | else 71 | 72 | %Greenhouse-Geisser estimate 73 | epsilon = NaN([n_electrodes, n_time_pts]); 74 | for e = 1:n_electrodes 75 | for t = 1:n_time_pts 76 | epsilon(e,t) = epsGG(squeeze(reduced_data(e, t, :, :))'); 77 | end 78 | end 79 | 80 | %Hyunh-Feldt 81 | if strcmpi(method, 'hf') 82 | epsilon = (n_subs*df_effect*epsilon - 2) ./ (df_effect*(n_subs-1-df_effect*epsilon)); 83 | epsilon(epsilon>1) = 1; 84 | end 85 | 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /fmut.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #Written in Python 3.7 3 | #Python 2 compliant as of 6/16/17 4 | #Most recently updated/tested in openpyxl 2.6 5 | 6 | """ 7 | Python functions for the Factorial Mass ERP Univariate Toolbox 8 | 9 | AUTHOR: Eric Fields 10 | VERSION DATE: 18 April 2019 11 | """ 12 | 13 | import sys 14 | import re 15 | import openpyxl 16 | from openpyxl.styles import Font, PatternFill, Alignment 17 | from openpyxl.utils import get_column_letter 18 | from openpyxl.formatting.rule import CellIsRule, ColorScaleRule 19 | 20 | if sys.version_info.major == 2: 21 | input = raw_input 22 | range = xrange 23 | 24 | def format_xls(spreadsheet): 25 | """ 26 | Take spreadsheet output from FMUT functions and apply formatting 27 | """ 28 | 29 | wb = openpyxl.load_workbook(spreadsheet) 30 | 31 | #Delete blank sheets 32 | for sheet_name in wb.sheetnames: 33 | if re.match(r'Sheet\d', sheet_name): 34 | sheet2remove = wb[sheet_name] 35 | wb.remove(sheet2remove) 36 | wb.active = 0 37 | 38 | #Define some fill styles for use below 39 | whiteFill = PatternFill(start_color='ffffff', end_color='ffffff', fill_type='solid') 40 | yellowHighlight = PatternFill(start_color='f7ff23', end_color='f7ff23', fill_type='solid') 41 | 42 | for sheet_name in wb.sheetnames: 43 | 44 | sheet = wb[sheet_name] 45 | 46 | #Test summary sheet 47 | if sheet_name == 'test summary': 48 | sheet.column_dimensions['A'].width = 40 49 | sheet.column_dimensions['B'].width = 200 50 | for cell in sheet['B']: 51 | cell.alignment = Alignment(horizontal='left') 52 | #Format critical values and get alpha level of test 53 | for row in range(1, sheet.max_row+1): 54 | if 'critical value' in sheet['A'+str(row)].value: 55 | sheet['B'+str(row)].number_format = '0.00' 56 | elif 'Alpha' in sheet['A'+str(row)].value: 57 | alpha = sheet['B'+str(row)].value 58 | 59 | #cluster_summary sheet 60 | if sheet_name == 'cluster summary': 61 | #Set column dimensions 62 | for i in range(1, sheet.max_column+1): 63 | if i % 3 == 1: 64 | sheet.column_dimensions[get_column_letter(i)].width = 19 65 | elif i % 3 == 2: 66 | sheet.column_dimensions[get_column_letter(i)].width = 10 67 | elif i % 3 == 0: 68 | sheet.column_dimensions[get_column_letter(i)].width = 5 69 | #Freeze header for easier viewing 70 | sheet.freeze_panes = sheet['A2'] 71 | #Set header style 72 | for cell in sheet[1]: 73 | cell.font = Font(sz=13, bold=True) 74 | #Format decimal places of stats 75 | for row in range(2, sheet.max_row+1): 76 | if any(cell.value=='cluster mass' for cell in sheet[str(row)]): 77 | for cell in sheet[row]: 78 | cell.number_format = '0.00' 79 | elif any(cell.value=='p-value' for cell in sheet[str(row)]): 80 | for cell in sheet[row]: 81 | cell.number_format = '0.000' 82 | #Left align everything for easier viewing 83 | for col in range(2, sheet.max_column+1): 84 | if col % 3 == 2: 85 | for row in range(3, sheet.max_row+1): 86 | sheet.cell(row=row, column=col).alignment = Alignment(horizontal='left') 87 | #Bold and highlight significant clusters 88 | for row in range(1, sheet.max_row+1): 89 | for cell in sheet[row]: 90 | if cell.value=='p-value' and cell.offset(row=0, column=1).value <= alpha: 91 | cell.offset(row=0, column=1).fill = yellowHighlight 92 | for r in range(cell.row-2, cell.row+7): 93 | for c in range(cell.column, cell.column+2): 94 | sheet.cell(row=r, column=c).font = Font(bold=True) 95 | 96 | 97 | #Some general stuff for remaining sheets 98 | if sheet_name.endswith('clust_IDs') or sheet_name.endswith('pvals') or sheet_name.endswith('_obs'): 99 | #Make headers bold 100 | for cell in sheet[1]: 101 | cell.font = Font(bold=True) 102 | cell.number_format = '0' 103 | for cell in sheet['A']: 104 | cell.font = Font(bold=True) 105 | #Freeze headers for easier viewing 106 | sheet.freeze_panes = sheet['B2'] 107 | 108 | #Format cluster ID sheets 109 | if sheet_name.endswith('clust_IDs'): 110 | #Reduce column width 111 | for i in range(1, sheet.max_column+1): 112 | sheet.column_dimensions[get_column_letter(i)].width = 4 113 | #Apply a different color to each cluster 114 | max_cell = get_column_letter(sheet.max_column) + str(sheet.max_row) 115 | clust_colors = ('36ec41', '003fbb', 'b4b500', 'c777ff', '00f7b8', '2f0067', '537e00', 'ff6dbf', '8befff', 'ff5227', '0171aa', 'a92900', '00727b', 'b60070', 'faffd1', '540042', 'a96500', 'e2caff', 'ffa177','6a001f') 116 | for c in range(100): 117 | color_idx = c % 20 118 | clust = c + 1 119 | clustFill = PatternFill(start_color=clust_colors[color_idx], end_color=clust_colors[color_idx], fill_type='solid') 120 | sheet.conditional_formatting.add('B2:'+max_cell, 121 | CellIsRule(operator='equal', formula=[clust], stopIfTrue=True, fill=clustFill)) 122 | #Reduce font size and clear locations not included in cluster 123 | for row in range(2, sheet.max_row+1): 124 | for cell in sheet[row]: 125 | if cell.column > 1: 126 | cell.font = Font(sz=10) 127 | if not cell.value: 128 | cell.value = None 129 | 130 | #Format t-obs F-obs sheets 131 | elif sheet_name.endswith('_obs'): 132 | #Reduce column width 133 | for i in range(1, sheet.max_column+1): 134 | sheet.column_dimensions[get_column_letter(i)].width = 6 135 | #Format numbers and apply conditional formatting 136 | max_cell = get_column_letter(sheet.max_column) + str(sheet.max_row) 137 | data_range = 'B2:'+max_cell 138 | for row in range(2, sheet.max_row+1): 139 | for cell in sheet[row]: 140 | if cell.column != 1: 141 | cell.number_format = '0.00' 142 | cell.font = Font(sz=10) 143 | if sheet_name.endswith('F_obs'): 144 | sheet.conditional_formatting.add(data_range, 145 | ColorScaleRule(start_type='num', start_value=1, start_color='ffffff', 146 | end_type='max', end_color='448452')) 147 | elif sheet_name.endswith('t_obs'): 148 | sheet.conditional_formatting.add(data_range, 149 | ColorScaleRule(start_type='min', start_color='0047ba', 150 | mid_type='num', mid_value=0, mid_color='ffffff', 151 | end_type='max', end_color='c63535')) 152 | 153 | #Format p-value sheets 154 | elif sheet_name.endswith('_pvals'): 155 | #Reduce column width 156 | for i in range(2, sheet.max_column+1): 157 | sheet.column_dimensions[get_column_letter(i)].width = 6 158 | #Format numbers and apply conditional formatting 159 | max_cell = get_column_letter(sheet.max_column) + str(sheet.max_row) 160 | data_range = 'B2:'+max_cell 161 | for row in range(2, sheet.max_row+1): 162 | for cell in sheet[row]: 163 | if cell.column != 1: 164 | cell.number_format = '0.000' 165 | cell.font = Font(sz=10) 166 | sheet.conditional_formatting.add(data_range, 167 | CellIsRule(operator='greaterThan', formula=[alpha], stopIfTrue=True, fill=whiteFill)) 168 | sheet.conditional_formatting.add(data_range, 169 | ColorScaleRule(start_type='num', start_value=0, start_color='4e875b', 170 | end_type='num', end_value=alpha, end_color='c9ffd5')) 171 | 172 | #Save 173 | wb.save(spreadsheet) 174 | 175 | if __name__ == '__main__': 176 | if len(sys.argv) > 3: 177 | raise RuntimeError('fmut.py can only take one argument-option pair.') 178 | elif '--format_xls' in sys.argv: 179 | format_xls(sys.argv[sys.argv.index('--format_xls')+1]) 180 | else: 181 | spreadsheet = input('To format an FMUT output spreadsheet, give the filename with the full path: ') 182 | format_xls(spreadsheet) 183 | -------------------------------------------------------------------------------- /format_xls.m: -------------------------------------------------------------------------------- 1 | %Add useful formatting to FMUT output spreadsheets 2 | % 3 | %EXAMPLE USAGE 4 | % >> format_xls('results.xlsx') 5 | % 6 | %REQUIRED INPUTS 7 | % spreadsheet - name of spreadsheet file to format 8 | % 9 | %VERSION DATE: 18 April 2019 10 | %AUTHOR: Eric Fields 11 | % 12 | %NOTE: This function is provided "as is" and any express or implied warranties 13 | %are disclaimed. 14 | 15 | %Copyright (c) 2017, Eric Fields 16 | %All rights reserved. 17 | %This code is free and open source software made available under the 3-clause BSD license. 18 | 19 | function format_xls(spreadsheet) 20 | 21 | %If full path is not provided assume spreadsheet is the current working 22 | %directory 23 | if ~isdir(fileparts(spreadsheet)) %#ok 24 | spreadsheet = [pwd filesep spreadsheet]; 25 | end 26 | 27 | %Check is spreadsheet exists and return gracefully if it doesn't 28 | if ~exist(spreadsheet, 'file') 29 | watchit(sprintf('Spreadsheet formatting error:\n%s does not exist.', spreadsheet)); 30 | return; 31 | end 32 | 33 | %Find FMUT directory 34 | func_dir = fileparts(which('format_xls')); 35 | 36 | %Try using .py script 37 | try 38 | py_addpath(func_dir); 39 | py.fmut.format_xls(spreadsheet); 40 | 41 | %If .py version doesn't work, use pyinstaller version 42 | catch 43 | try 44 | if ispc() 45 | system(sprintf('"%s\\py_fmut.exe" --format_xls "%s"', fullfile(func_dir, 'py_fmut_win'), spreadsheet)); 46 | else 47 | system(sprintf('"%s/py_fmut" --format_xls "%s"', fullfile(func_dir, 'py_fmut_mac'), spreadsheet)); 48 | end 49 | catch 50 | watchit(sprintf('Unable to format spreadsheet.')) 51 | end 52 | 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /get_effects.m: -------------------------------------------------------------------------------- 1 | %Find all effects for a given repeated-measures ANOVA design. 2 | % 3 | %EXAMPLE USAGE 4 | % >> [effects, effects_labels] = get_effects({'Frequency', 'Priming'}) 5 | % 6 | %REQUIRED INPUTS 7 | % factor_names - A cell array of strings of the names of all factors in 8 | % the design 9 | % 10 | %OUTPUTS 11 | % effects - An array with each row specifying the dimensions of the 12 | % data array that are involved in each effect 13 | % effects labels - A matching cell array with strings describing each effect 14 | % 15 | %VERSION DATE: 28 November 2016 16 | %AUTHOR: Eric Fields 17 | % 18 | %NOTE: This function is provided "as is" and any express or implied warranties 19 | %are disclaimed. 20 | 21 | %Copyright (c) 2017, Eric Fields 22 | %All rights reserved. 23 | %This code is free and open source software made available under the 3-clause BSD license. 24 | 25 | function [effects, effects_labels] = get_effects(factor_names) 26 | %Given factor names, return dimensions involved and effect names for all effects 27 | 28 | %Get numerical coding of all main effects and interactions 29 | effects = {}; 30 | for i = 1:length(factor_names) 31 | effects = [effects; num2cell(nchoosek(1:length(factor_names), i), 2)]; %#ok 32 | end 33 | 34 | %Get effect names for all the effects described above 35 | effects_labels = cell(size(effects)); 36 | for i = 1:length(effects) 37 | if verLessThan('matlab','8.1') 38 | %Workaround for the fact that strjoin doesn't exist in older versions of MATLAB 39 | effects_labels{i} = ''; 40 | for j = 1:length(effects{i}) 41 | if j < length(effects{i}) 42 | effects_labels{i} = [effects_labels{i} factor_names{effects{i}(j)} 'X']; 43 | else 44 | effects_labels{i} = [effects_labels{i} factor_names{effects{i}(j)}]; 45 | end 46 | end 47 | else 48 | effects_labels{i} = strjoin(factor_names(effects{i}), 'X'); 49 | end 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /get_int_res.m: -------------------------------------------------------------------------------- 1 | %Calculate the interaction residuals for the highest order interaction in 2 | %the data. If the data has two factors, the main effects will be 3 | %subtracted. If the data has three factors, the main effects and two-way 4 | %interactions will be subtracted. 5 | % 6 | %EXAMPLE USAGE 7 | % >> int_res = get_int_res(data, [], [3, 4]) 8 | % 9 | %REQUIRED INPUTS 10 | % data - An electrode x time points x conditions x subjects array of ERP 11 | % data. Array will vary in number of dimensions based on how many 12 | % factors there are 13 | % cond_subs - Array giving the number of subjects in each condition of 14 | % the between subjects factor. For example, if cond_subs is 15 | % [8, 9], then there should be 17 subjects with first 8 16 | % being in condition A and the next 9 being in condition B 17 | % [] indicates a within-subjects ANOVA 18 | % dims - Dimensions of the data array involved in the effect 19 | % 20 | %OUTPUT 21 | % int_res - interaction residuals: the data with all lower order 22 | % effects subtracted out 23 | % 24 | % 25 | %VERSION DATE: 14 July 2017 26 | %AUTHOR: Eric Fields 27 | % 28 | %NOTE: This function is provided "as is" and any express or implied warranties 29 | %are disclaimed. 30 | 31 | %Copyright (c) 2017, Eric Fields 32 | %All rights reserved. 33 | %This code is free and open source software made available under the 3-clause BSD license. 34 | 35 | function int_res = get_int_res(data, cond_subs, dims) 36 | 37 | if isempty(cond_subs) || length(cond_subs)==1 || ~any(dims == ndims(data)) 38 | 39 | %#################################### 40 | %### WITHIN SUBJECTS FACTORS ONLY ### 41 | %#################################### 42 | 43 | %Interaction of two within-subjects factors 44 | if ndims(data) == 5 && isequal(dims, [3, 4]) 45 | int_res = twoway_rb_main(data); 46 | %Interaction of three within-subjects factors 47 | elseif ndims(data) == 6 && isequal(dims, [3, 4, 5]) 48 | int_res1 = threeway_rb_main(data); 49 | int_res = threeway_rb_int(int_res1); 50 | end 51 | 52 | else 53 | 54 | %#################################### 55 | %######## SPLIT PLOT DESIGNS ######## 56 | %#################################### 57 | 58 | if ndims(data) == 4 59 | %Interaction of one within-subjects factor and one between-subjects factor 60 | int_res = twoway_sp_main(data, cond_subs); 61 | elseif ndims(data) == 5 62 | if isequal(dims, [3, 4]) 63 | %Interaction of two within-subjects in the presence of a between-subjects factor 64 | int_res = twoway_rb_main(data, cond_subs); 65 | elseif isequal(dims, [3, 4, 5]) 66 | %Interaction of two within-subjects factors and one between-subjects factor 67 | int_res1 = threeway_sp_main(data, cond_subs); 68 | int_res = threeway_sp_int(int_res1, cond_subs); 69 | end 70 | end 71 | 72 | end 73 | 74 | assert(abs(mean(int_res(:))) < 1e-9) 75 | 76 | end 77 | 78 | 79 | function int_res = twoway_rb_main(data) 80 | %Subtract main effects from two-way within-subjects design 81 | [~, ~, n_conds_A, n_conds_B, ~] = size(data); 82 | int_res = NaN(size(data)); 83 | %Subtract main effects 84 | for p = 1:n_conds_A 85 | for q = 1:n_conds_B 86 | int_res(:,:, p, q, :) = data(:,:,p,q,:) ... 87 | - mean(data(:,:,p,:,:), 4) ... 88 | - mean(data(:,:,:,q,:), 3) ... 89 | + mean(mean(data, 3), 4); 90 | end 91 | end 92 | end 93 | 94 | function int_res = threeway_rb_main(data) 95 | %Subtract main effects from three-way within-subjects design 96 | [~, ~, n_conds_A, n_conds_B, n_conds_C, ~] = size(data); 97 | int_res = NaN(size(data)); 98 | %Subtract main effects 99 | for p = 1:n_conds_A 100 | for q = 1:n_conds_B 101 | for r = 1:n_conds_C 102 | int_res(:,:,p,q,r,:) = data(:,:,p,q,r,:) ... 103 | - mean(mean(data(:,:,p,:,:,:), 4), 5) ... 104 | - mean(mean(data(:,:,:,q,:,:), 3), 5) ... 105 | - mean(mean(data(:,:,:,:,r,:), 3), 4) ... 106 | + 2*mean(mean(mean(data, 3), 4), 5); 107 | end 108 | end 109 | end 110 | end 111 | 112 | 113 | function int_res = threeway_rb_int(data) 114 | %Subtract two-way interaction effects from three-way within-subjects design 115 | [~, ~, n_conds_A, n_conds_B, n_conds_C, ~] = size(data); 116 | int_res = NaN(size(data)); 117 | for p = 1:n_conds_A 118 | for q = 1:n_conds_B 119 | for r = 1:n_conds_C 120 | int_res(:,:,p,q,r,:) = data(:,:,p,q,r,:) ... 121 | - mean(data(:,:,p,q,:,:), 5) ... 122 | - mean(data(:,:,p,:,r,:), 4) ... 123 | - mean(data(:,:,:,q,r,:), 3) ... 124 | + 2*mean(mean(mean(data, 3), 4), 5); 125 | end 126 | end 127 | end 128 | end 129 | 130 | 131 | function int_res = twoway_sp_main(data, cond_subs) 132 | %Subtract main effects from two-way split plot design 133 | n_conds_A = length(cond_subs); 134 | n_conds_B = size(data, 3); 135 | int_res = NaN(size(data)); 136 | for p = 1:n_conds_A 137 | first = sum(cond_subs(1:p)) - cond_subs(p) + 1; 138 | for q = 1:n_conds_B 139 | for n = 1:cond_subs(p) 140 | sub = first+n-1; 141 | int_res(:,:,q,sub) = data(:,:,q,sub) ... 142 | - mean(data(:,:,q, :), 4) ... 143 | - mean(data(:, :, :, sub), 3) ... 144 | + mean(mean(data, 3), 4); 145 | end 146 | end 147 | end 148 | end 149 | 150 | 151 | function int_res = threeway_sp_main(data, cond_subs) 152 | %Subtract main effects from three-way split plot design 153 | n_conds_A = length(cond_subs); 154 | [~, ~, n_conds_B, n_conds_C, ~] = size(data); 155 | int_res = NaN(size(data)); 156 | for p = 1:n_conds_A 157 | first = sum(cond_subs(1:p)) - cond_subs(p) + 1; 158 | for q = 1:n_conds_B 159 | for r = 1:n_conds_C 160 | for n = 1:cond_subs(p) 161 | sub = first+n-1; 162 | int_res(:, :, q, r, sub) = data(:, :, q, r, sub) ... 163 | - mean(mean(data(:, :, q, :, :), 4), 5) ... 164 | - mean(mean(data(:, :, :, r, :), 3), 5) ... 165 | - mean(mean(data(:, :, :, :, sub), 3), 4) ... 166 | + 2*mean(mean(mean(data, 3), 4), 5); 167 | end 168 | end 169 | end 170 | end 171 | end 172 | 173 | 174 | function int_res = threeway_sp_int(data, cond_subs) 175 | %Subtract two-way interaction from three-way split plot design 176 | n_conds_A = length(cond_subs); 177 | [~, ~, n_conds_B, n_conds_C, ~] = size(data); 178 | int_res = NaN(size(data)); 179 | for p = 1:n_conds_A 180 | first = sum(cond_subs(1:p)) - cond_subs(p) + 1; 181 | for q = 1:n_conds_B 182 | for r = 1:n_conds_C 183 | for n = 1:cond_subs(p) 184 | sub = first+n-1; 185 | int_res(:, :, q, r, sub) = data(:, :, q, r, sub) ... 186 | - mean(data(:, :, q, r, :), 5) ... 187 | - mean(data(:, :, q, :, sub), 4) ... 188 | - mean(data(:, :, :, r, sub), 3) ... 189 | + 2 * mean(mean(mean(data, 3), 4), 5); 190 | end 191 | end 192 | end 193 | end 194 | end -------------------------------------------------------------------------------- /get_mean_amplitude.m: -------------------------------------------------------------------------------- 1 | %Function to get amplitude averaged across the time points and electrodes 2 | %that were significant in a mass univariate test. Mean amplitude is 3 | %returned for each subject and bin involved in the test. 4 | % 5 | %Note that care should be taken with regard to analyses conducted on such 6 | %mean amplitudes: 7 | %If analyses are not statistically indpendent of the original mass univariate 8 | %test, results will be biased. See: 9 | %Kriegeskorte, N., Simmons, W. K., Bellgowan, P. S., & Baker, C. I. (2009). 10 | %Circular analysis in systems neuroscience: the dangers of double dipping. 11 | %Nature Neuroscience, 12(5), 535. https://doi.org/10.1038/nn.2303 12 | % 13 | %EXAMPLE USAGE 14 | % >> mean_amp_data = get_mean_amplitude(GND, 2, 'effect', 'Congruency'); 15 | % 16 | %REQUIRED INPUTS 17 | % GND_or_fname - A Mass Univariate Toolbox GND or GRP struct or a string 18 | % containing a filename of a GND or GRP structure that 19 | % has been saved to disk (with full path if not in current 20 | % working directory) 21 | % test_id - [integer] The index # of the F-test results 22 | % stored in the GND/GRP/specGND variable 23 | % To see what test results are available, look at 24 | % the "F_tests" field of your variable (e.g., GND.F_tests) 25 | % 26 | %OPTIONAL INPUTS: 27 | % effect - Name of the effect of interest (e.g., 'Probability or 28 | % 'ProbabilityXEmotion'). Required for factorial ANOVA. 29 | % clust_id - For cluster mass tests, which cluster to average 30 | % across {default: all significant clusters} 31 | % bins - The bins to get data from. {default: all bins used in 32 | % the F-test} 33 | % output_file - Name of .csv file to output results. {default: no output} 34 | % 35 | %OUTPUT 36 | % mean_amp_data - struct with the requested data as well as bin and 37 | % subject information 38 | % 39 | %AUTHOR: Eric Fields 40 | %VERSION DATE: 4 March 2020 41 | % 42 | %NOTE: This function is provided "as is" and any express or implied warranties 43 | %are disclaimed. 44 | 45 | %Copyright (c) 2018, Eric Fields 46 | %All rights reserved. 47 | %This code is free and open source software made available under the 3-clause BSD license. 48 | 49 | function mean_amp_data = get_mean_amplitude(GND_or_fname, test_id, varargin) 50 | 51 | %% PARSE INPUT 52 | 53 | p=inputParser; 54 | p.addRequired('GND_or_fname', @(x) ischar(x) || isstruct(x)); 55 | p.addRequired('test_id', @(x) isnumeric(x) && length(x)==1); 56 | p.addParameter('effect', [], @(x) ischar(x)); 57 | p.addParameter('clust_id', [], @(x) isnumeric(x)); 58 | p.addParameter('bins', [], @(x) isnumeric(x)); 59 | p.addParameter('output_file', false); 60 | p.parse(GND_or_fname, test_id, varargin{:}); 61 | 62 | %Assign GND 63 | if ischar(GND_or_fname) && exists(GND_or_fname, 'file') 64 | load(GND_or_fname, '-mat'); %#ok 65 | if exists('GRP', 'var') 66 | GNDorGRP = GRP; 67 | clear GRP; 68 | elseif exists('GND', 'var') 69 | GNDorGRP = GND; %#ok 70 | else 71 | error('Did not find a GND or GRP variable in %s', GND_or_fname); 72 | end 73 | elseif isstruct(GND_or_fname) 74 | GNDorGRP = GND_or_fname; 75 | else 76 | error('The GND variable provided does not seem to be a valid GND struct or filepath to a GND struct.'); 77 | end 78 | 79 | %Load all GNDs in a GRP variable 80 | if isfield(GNDorGRP, 'GND_fnames') 81 | GNDs = {}; 82 | for i = 1:length(GNDorGRP.GND_fnames) 83 | load(GNDorGRP.GND_fnames{i}, '-mat') 84 | GNDs{i} = GND; %#ok 85 | end 86 | clear GND 87 | else 88 | GNDs = {GNDorGRP}; 89 | end 90 | 91 | %Assign variables 92 | effect = p.Results.effect; 93 | clust_id = p.Results.clust_id; 94 | bins = p.Results.bins; 95 | output_file = p.Results.output_file; 96 | results = GNDorGRP.F_tests(test_id); 97 | if isempty(effect) 98 | null_test = results.null_test; 99 | clust_info = results.clust_info; 100 | else 101 | null_test = results.null_test.(effect); 102 | clust_info = results.null_test.(effect); 103 | end 104 | 105 | %Assign defaults 106 | if isempty(bins) 107 | bins = results.bins; 108 | end 109 | 110 | %Check for errors in input 111 | if length(results.factor_levels) > 1 && isempty(effect) 112 | error('You must specify which effect you want data from. See >> help get_sub_effects'); 113 | end 114 | if ~isempty(clust_id) 115 | if ~strcmpi(results.mult_comp_method, 'cluster mass perm test') 116 | error('Test %d in the GND is not a cluster mass test', test_id); 117 | elseif clust_id > length(clust_info.null_test) 118 | error('There is no cluster %d for the %s effect', clust_id, effect); 119 | end 120 | end 121 | 122 | %% GET SUBJECT DATA 123 | 124 | %Initialize output 125 | mean_amp_data = struct; 126 | mean_amp_data.subjects = {}; 127 | mean_amp_data.bins = bins; 128 | mean_amp_data.bindesc = {GNDorGRP.bin_info(bins).bindesc}; 129 | mean_amp_data.data = []; 130 | 131 | %useful numbers 132 | n_electrodes = length(GNDorGRP.chanlocs); 133 | n_time_pts = length(GNDorGRP.time_pts); 134 | n_bins = length(bins); 135 | 136 | %Find locations that are significant 137 | sig_locs = zeros(n_electrodes, n_time_pts); 138 | if isempty(clust_id) 139 | sig_locs(results.used_chan_ids, results.used_tpt_ids) = null_test; 140 | else 141 | sig_locs(results.used_chan_ids, results.used_tpt_ids) = ismember(clust_info.clust_ids, clust_id); 142 | end 143 | sig_locs = logical(sig_locs); 144 | 145 | %Extract mean amplitudes 146 | for i = 1:length(GNDs) 147 | GND = GNDs{i}; 148 | n_subs = size(GND.indiv_erps, 4); 149 | if length(GNDs) == 1 150 | mean_amp_data.subjects = [mean_amp_data.subjects; GND.indiv_subnames']; 151 | else 152 | mean_amp_data.subjects = [mean_amp_data.subjects; repmat(GNDorGRP.group_desc(i), [n_subs,1]) GND.indiv_subnames']; 153 | end 154 | group_data = NaN(n_subs, n_bins); 155 | for s = 1:n_subs 156 | for b = 1:n_bins 157 | bin = bins(b); 158 | data = GND.indiv_erps(:, :, bin, s); 159 | group_data(s, b) = mean(data(sig_locs)); 160 | end 161 | end 162 | assert(~any(isnan(group_data(:)))); 163 | mean_amp_data.data = [mean_amp_data.data; group_data]; 164 | end 165 | 166 | %% OUTPUT 167 | 168 | if output_file 169 | 170 | %Get rid of commas to avoid problems in csv formatting 171 | bin_info = cellfun(@(x) strrep(x, ',', ''), mean_amp_data.bindesc, 'UniformOutput', false); 172 | sub_info = cellfun(@(x) strrep(x, ',', ''), mean_amp_data.subjects, 'UniformOutput', false); 173 | 174 | f_out = fopen(output_file, 'w'); 175 | if length(GNDs) == 1 176 | %Within-subjects 177 | fprintf(f_out, 'subject_ids,%s\n', strjoin(bin_info, ',')); %header 178 | for s = 1:size(mean_amp_data.data, 1) 179 | data_str = num2str(mean_amp_data.data(s,:),'%f,'); %convert data to comma separated string 180 | data_str = data_str(1:end-1); %remove trailing comma 181 | fprintf(f_out, '%s,%s\n', sub_info{s}, data_str); %write line for subject s 182 | end 183 | else 184 | %Betwen-subjects 185 | fprintf(f_out,'subject_ids,group,%s\n', strjoin(bin_info, ',')); %header 186 | for s = 1:size(mean_amp_data.data, 1) 187 | data_str = num2str(mean_amp_data.data(s,:),'%f,'); %convert data to comma separated string 188 | data_str = data_str(1:end-1); %remove trailing comma 189 | fprintf(f_out, '%s,%s,%s\n', sub_info{s,2}, sub_info{s,1}, data_str); %write line for subject s 190 | end 191 | end 192 | fclose(f_out); 193 | 194 | end 195 | 196 | end 197 | -------------------------------------------------------------------------------- /get_poi_paths.m: -------------------------------------------------------------------------------- 1 | %Return the full paths of the .jar files of the Apache POI library 2 | % 3 | %EXAMPLE USAGE 4 | % >> get_poi_paths 5 | % 6 | %OUTPUT 7 | % poi_files - Full paths for all .jar files in the Apache POI 8 | % library 9 | % missing_poi_files - Full paths for .jar files in the Apache POI library 10 | % that are not on the static Java class path 11 | % 12 | %VERSION DATE: 20 November 2017 13 | %AUTHOR: Eric Fields 14 | % 15 | %NOTE: This function is provided "as is" and any express or implied warranties 16 | %are disclaimed. 17 | 18 | %Copyright (c) 2017, Eric Fields 19 | %All rights reserved. 20 | %This code is free and open source software made available under the 3-clause BSD license. 21 | 22 | function [poi_files, missing_poi_files] = get_poi_paths() 23 | 24 | %Find directory with POI library 25 | poi_dir = fullfile(fileparts(which('get_poi_paths')), 'poi_library'); 26 | 27 | %Find all .jar files within directory 28 | poi_files = dir(poi_dir); 29 | poi_files = {poi_files.name}; 30 | poi_files = poi_files(cell2mat(cellfun(@(x) ~isempty(strfind(x, '.jar')), poi_files, 'UniformOutput', false))); %#ok 31 | poi_files = cellfun(@(x) [poi_dir filesep x], poi_files, 'UniformOutput', false); 32 | 33 | %Find which .jar files (if any) are not on the static Java class path 34 | missing_poi_files = poi_files(~ismember(poi_files, javaclasspath('-static'))); 35 | 36 | end -------------------------------------------------------------------------------- /perm_crANOVA.m: -------------------------------------------------------------------------------- 1 | %Calculate F-observed and the empirical F-distribution for a one-way 2 | %between subjects ANOVA 3 | % 4 | %EXAMPLE USAGE 5 | % >> [F_obs, F_dist, df_effect, df_res] = perm_crANOVA(data, [16, 16], 1e4) 6 | % 7 | %REQUIRED INPUTS 8 | % data - An electrode x time points x conditions x subjects array of ERP 9 | % data. Array will vary in number of dimensions based on how many 10 | % factors there are 11 | % cond_subs - Array giving the number of subjects in each condition of 12 | % the between subjects factor. For example, if cond_subs is 13 | % [8, 9], then there should be 17 subjects with first 8 14 | % being in condition A and the next 9 being in condition B 15 | % n_perm - Number of permutations to conduct 16 | % 17 | %OUTPUT 18 | % F_obs - electrode x time point matrix of unpermuted F-values 19 | % F_dist - F-values at each time point and electrode for each 20 | % permutation. The first permutation is F-observed. 21 | % df_effect - numerator degrees of freedom 22 | % df_res - denominator degrees of freedom 23 | % 24 | % 25 | %VERSION DATE: 4 April 2019 26 | %AUTHOR: Eric Fields 27 | % 28 | %NOTE: This function is provided "as is" and any express or implied warranties 29 | %are disclaimed. 30 | 31 | %Copyright (c) 2017, Eric Fields 32 | %All rights reserved. 33 | %This code is free and open source software made available under the 3-clause BSD license. 34 | 35 | 36 | function [F_obs, F_dist, df_effect, df_res] = perm_crANOVA(data, cond_subs, n_perm) 37 | 38 | global VERBLEVEL 39 | 40 | %Make sure there's only one factor 41 | assert(ndims(data) == 3); 42 | 43 | %Some useful numbers 44 | [n_electrodes, n_time_pts, n_subs] = size(data); 45 | n_conds = length(cond_subs); 46 | if sum(cond_subs) ~= n_subs 47 | error('The number of subjects in the ''cond_subs'' input doesn''t match the number of subjects in the data'); 48 | end 49 | 50 | %Calculate degrees of freedom 51 | %(Always the same, so no point calculating in the loop) 52 | dfA = n_conds - 1; 53 | dfRES = n_subs - n_conds; 54 | 55 | %Perform n_perm permutations 56 | F_dist = NaN(n_perm, n_electrodes, n_time_pts); 57 | 58 | for i = 1:n_perm 59 | 60 | %Permute the data 61 | if i ==1 62 | perm_data = data; 63 | else 64 | perm_data = data(:, :, randperm(size(data, 3))); 65 | end 66 | 67 | %Calculate sums of squares 68 | A = 0; 69 | for a = 1:n_conds 70 | first = sum(cond_subs(1:a)) - cond_subs(a) + 1; 71 | last = sum(cond_subs(1:a)); 72 | A = A + ((sum(perm_data(:, :, first:last), 3).^2) / cond_subs(a)); 73 | end 74 | SSyint = (sum(perm_data, 3).^2) / n_subs; 75 | SSTO = sum(perm_data.^2, 3) - SSyint; 76 | SSA = A - SSyint; 77 | SSRES = SSTO - SSA; 78 | 79 | %Calculate F 80 | SSA(SSA < 1e-12) = 0; %Eliminates large F values that result from floating point error 81 | F_dist(i, :, :) = (SSA/dfA) ./ (SSRES/dfRES); 82 | 83 | if VERBLEVEL 84 | if i == 1 && n_perm > 1 85 | fprintf('Permutations completed: ') 86 | elseif i == n_perm && n_perm > 1 87 | fprintf('%d\n', i) 88 | elseif ~mod(i, 1000) 89 | fprintf('%d, ', i) 90 | end 91 | end 92 | 93 | end 94 | 95 | %Extract unpermuted F-values 96 | F_obs = reshape(F_dist(1, :, :), [n_electrodes, n_time_pts]); 97 | 98 | %degrees of freedom 99 | df_effect = dfA; 100 | df_res = dfRES; 101 | 102 | end 103 | -------------------------------------------------------------------------------- /perm_rbANOVA.m: -------------------------------------------------------------------------------- 1 | %Calculate F-observed and the empirical F-distribution for a 2 | %within-subjects ANOVA with up to three factors. This function calculates a 3 | %one-way ANOVA, two-way interaction, and three-way interaction. 4 | % 5 | %EXAMPLE USAGE 6 | % >> [F_obs, F_dist, df_effect, df_res, exact_test] = perm_rbANOVA(data, [3, 4], 1e4) 7 | % 8 | %REQUIRED INPUTS 9 | % data - An electrode x time points x conditions x subjects array of ERP 10 | % data. Array will vary in number of dimensions based on how many 11 | % factors there are. 12 | % dims - Dimensions involved in the effect. Given the structure of 13 | % the data specified above, for a two-way ANOVA 3 indicates 14 | % the within-subjects factors, 4 indicates the between 15 | % subjects factors, and [3, 4] indicates the interaction. 16 | % n_perm - Number of permutations to conduct 17 | % 18 | %OUTPUT 19 | % F_obs - electrodes x time point matrix of F-values 20 | % F_dist - F-values at each time point and electrode for each 21 | % permutation. 22 | % df_effect - numerator degrees of freedom 23 | % df_res - denominator degrees of freedom 24 | % exact_test - Boolean specifying whether the test was an exact test 25 | % 26 | % 27 | %VERSION DATE: 4 April 2019 28 | %AUTHOR: Eric Fields 29 | % 30 | %NOTE: This function is provided "as is" and any express or implied warranties 31 | %are disclaimed. 32 | 33 | %Copyright (c) 2017, Eric Fields 34 | %All rights reserved. 35 | %This code is free and open source software made available under the 3-clause BSD license. 36 | 37 | function [F_obs, F_dist, df_effect, df_res, exact_test] = perm_rbANOVA(data, dims, n_perm, reduce) 38 | 39 | %Eliminate factors not involved in this effect and reduce interactions 40 | %via subtraction 41 | if nargin < 4 42 | reduce = true; 43 | end 44 | if reduce 45 | reduced_data = reduce_data(data, dims); 46 | else 47 | reduced_data = data; 48 | end 49 | 50 | %Calculate appropriate ANOVA 51 | if ndims(reduced_data) == 4 52 | [F_obs, F_dist, df_effect, df_res] = oneway(reduced_data, n_perm); 53 | exact_test = true; 54 | elseif ndims(reduced_data) == 5 55 | [F_obs, F_dist, df_effect, df_res] = twoway_approx_int(reduced_data, n_perm); 56 | exact_test = false; 57 | elseif ndims(reduced_data) == 6 58 | [F_obs, F_dist, df_effect, df_res] = threeway_approx_int(reduced_data, n_perm); 59 | exact_test = false; 60 | end 61 | 62 | end 63 | 64 | function [F_obs, F_dist, df_effect, df_res] = oneway(data, n_perm) 65 | %Perform permutation one-way ANOVA. This is an exact test. 66 | 67 | global VERBLEVEL 68 | 69 | %Make sure there's only one factor 70 | assert(ndims(data) == 4); 71 | 72 | %Some useful numbers 73 | [n_electrodes, n_time_pts, n_conds, n_subs] = size(data); 74 | 75 | %Calculate degrees of freedom 76 | %(Always the same, so no point calculating in the loop) 77 | dfA = n_conds - 1; 78 | dfBL = n_subs - 1; 79 | dfRES = dfA * dfBL; 80 | 81 | %Perform n_perm permutations 82 | F_dist = NaN(n_perm, n_electrodes, n_time_pts); 83 | for i = 1:n_perm 84 | 85 | %Permute the data 86 | if i ==1 87 | perm_data = data; 88 | else 89 | for n = 1:n_subs 90 | perm_data(:, :, :, n) = data(:, :, randperm(size(data, 3)), n); 91 | end 92 | end 93 | 94 | %Calculate sums of squares 95 | SSyint = (sum(sum(perm_data, 3), 4).^2) / (n_conds * n_subs); 96 | %SSTO = sum(sum(perm_data.^2, 3), 4) - SSyint; 97 | SSA = (sum(sum(perm_data, 4).^2, 3) / n_subs) - SSyint; 98 | SSBL = (sum(sum(perm_data, 3).^2, 4) / n_conds) - SSyint; 99 | SSRES = sum(sum(perm_data.^2, 3), 4) - SSA - SSBL - SSyint; 100 | %assert(all(abs(SSTO(:) - (SSA(:) + SSBL(:) + SSRES(:))) < 1e-9)); 101 | 102 | %Calculate F 103 | SSA(SSA < 1e-12) = 0; %Eliminates large F values that result from floating point error 104 | F_dist(i, :, :) = (SSA/dfA) ./ (SSRES/dfRES); 105 | 106 | if VERBLEVEL 107 | if i == 1 && n_perm > 1 108 | fprintf('Permutations completed: ') 109 | elseif i == n_perm && n_perm > 1 110 | fprintf('%d\n', i) 111 | elseif ~mod(i, 1000) 112 | fprintf('%d, ', i) 113 | end 114 | end 115 | 116 | end 117 | 118 | %Extract unpermuted F-values 119 | F_obs = reshape(F_dist(1, :, :), [n_electrodes, n_time_pts]); 120 | 121 | %degrees of freedom 122 | df_effect = dfA; 123 | df_res = dfRES; 124 | 125 | end 126 | 127 | 128 | function [F_obs, F_dist, df_effect, df_res] = twoway_approx_int(data, n_perm) 129 | %Use permutation of residuals method to conduct an approximate test of the 130 | %two-way interaction. 131 | 132 | global VERBLEVEL 133 | 134 | %Make sure we're dealing with a two-way design 135 | assert(ndims(data) == 5); 136 | 137 | %Some useful numbers 138 | [n_electrodes, n_time_pts, n_conds_A, n_conds_B, n_subs] = size(data); 139 | 140 | %Subtract main effects within each subject so that the data is 141 | %exchangeable under the null hypothesis for the interaction 142 | int_res = get_int_res(data, [], [3, 4]); 143 | 144 | %Calculate degrees of freedom 145 | %(Always the same, so no point calculating in the loop) 146 | dfBL = n_subs - 1; 147 | dfA = n_conds_A - 1; 148 | %dfAerr = dfA * dfBL; 149 | dfB = n_conds_B - 1; 150 | %dfBerr = dfB * dfBL; 151 | dfAxB = dfA * dfB; 152 | dfAxBerr = dfAxB * dfBL; 153 | %dfRES = (num_subs - 1) * (num_conds_A * num_conds_B - 1); 154 | 155 | %Re-arrange data for permutation 156 | flat_data = reshape(int_res, [n_electrodes, n_time_pts, n_conds_A*n_conds_B, n_subs]); 157 | 158 | %Perform n_perm permutations 159 | F_dist = NaN(n_perm, n_electrodes, n_time_pts); 160 | flat_perm_data = NaN(size(flat_data)); 161 | for i = 1:n_perm 162 | %Permute the data 163 | if i == 1 164 | perm_data = int_res; 165 | else 166 | for s = 1:n_subs 167 | flat_perm_data(:, :, :, s) = flat_data(:, :, randperm(size(flat_data, 3)), s); 168 | end 169 | perm_data = reshape(flat_perm_data, n_electrodes, n_time_pts, n_conds_A, n_conds_B, n_subs); 170 | end 171 | 172 | %Calculate sums of squares 173 | SSyint = (sum(sum(sum(perm_data, 3), 4), 5).^2)/(n_conds_A*n_conds_B*n_subs); 174 | %SSTO = sum(sum(sum(perm_data.^2, 3), 4), 5) - SSyint; 175 | SSA = sum(sum(sum(perm_data, 4), 5).^2, 3)/(n_conds_B*n_subs) - SSyint; 176 | SSB = sum(sum(sum(perm_data, 3), 5).^2, 4)/(n_conds_A*n_subs) - SSyint; 177 | SSBL = sum(sum(sum(perm_data, 3), 4).^2, 5)/(n_conds_A*n_conds_B) - SSyint; 178 | SSAxB = sum(sum(sum(perm_data, 5).^2, 3), 4)/n_subs - SSA - SSB -SSyint; 179 | SSAxBL = sum(sum(sum(perm_data, 4).^2, 3), 5)/n_conds_B - SSA - SSBL - SSyint; 180 | SSBxBL = sum(sum(sum(perm_data, 3).^2, 4), 5)/n_conds_A - SSB - SSBL - SSyint; 181 | SSAxBxBL = sum(sum(sum(perm_data.^2, 3), 4), 5) - SSA - SSB - SSBL - SSAxB - SSAxBL - SSBxBL - SSyint; 182 | %SSRES = sum(sum(sum(perm_data.^2, 3), 4), 5) - SSA - SSB - SSBL - SSAxB - SSyint; 183 | 184 | %Doublechecking that the numbers match up 185 | %assert(all(SSRES - (SSAxBL + SSBxBL + SSAxBxBL) < 1e-9)); %SSRES is equal to its three subcomponents 186 | %assert(all(SSTO - (SSRES + SSBL + SSA + SSB + SSAxB) < 1e-9)); %sums of squares add up 187 | 188 | %Calculate F 189 | SSAxB(SSAxB < 1e-12) = 0; %Eliminates large F values that result from floating point error 190 | F_dist(i, :, :) = (SSAxB/dfAxB) ./ (SSAxBxBL/dfAxBerr); 191 | 192 | if VERBLEVEL 193 | if i == 1 && n_perm > 1 194 | fprintf('Permutations completed: ') 195 | elseif i == n_perm && n_perm > 1 196 | fprintf('%d\n', i) 197 | elseif ~mod(i, 1000) 198 | fprintf('%d, ', i) 199 | end 200 | end 201 | 202 | end 203 | 204 | %Extract unpermuted F-values 205 | F_obs = reshape(F_dist(1, :, :), [n_electrodes, n_time_pts]); 206 | 207 | %degrees of freedom 208 | df_effect = dfAxB; 209 | df_res = dfAxBerr; 210 | 211 | end 212 | 213 | 214 | function [F_obs, F_dist, df_effect, df_res] = threeway_approx_int(data, n_perm) 215 | %Use permutation of residuals method to conduct an approximate test of the 216 | %three-way interaction. 217 | 218 | global VERBLEVEL 219 | 220 | %Make sure we're dealing with a two-way design 221 | assert(ndims(data) == 6); 222 | 223 | %Some useful numbers 224 | [n_electrodes, n_time_pts, n_conds_A, n_conds_B, n_conds_C, n_subs] = size(data); 225 | 226 | %Subtract main effects then two-way effects within each subject so that 227 | %the data is exchangeable under the null hypothesis for the three-way 228 | %interaction 229 | int_res = get_int_res(data, [], [3, 4, 5]); 230 | 231 | %Calculate degrees of freedom 232 | %(Always the same, so no point calculating in the loop) 233 | dfBL = n_subs - 1; 234 | dfA = n_conds_A - 1; 235 | %dfAerr = dfA * dfBL; 236 | dfB = n_conds_B - 1; 237 | %dfBerr = dfB * dfBL; 238 | dfC = n_conds_C - 1; 239 | %dfCerr = dfC * dfBL; 240 | %dfAxB = dfA * dfB; 241 | %dfAxBerr = dfAxB * dfBL; 242 | %dfAxC = dfA * dfC; 243 | %dfAxCerr = dfAxC * dfBL; 244 | %dfBxC = dfB * dfC; 245 | %dfBxCerr = dfBxC * dfBL; 246 | dfAxBxC = dfA * dfB * dfC; 247 | dfAxBxCerr = dfAxBxC * dfBL; 248 | %dfRES = (n_subs - 1) * (n_conds_A * n_conds_B * n_conds_C - 1); 249 | 250 | %Re-arrange data for permutation 251 | flat_data = reshape(int_res, [n_electrodes, n_time_pts, n_conds_A*n_conds_B*n_conds_C, n_subs]); 252 | 253 | %Perform n_perm permutations 254 | F_dist = NaN(n_perm, n_electrodes, n_time_pts); 255 | flat_perm_data = NaN(size(flat_data)); 256 | for i = 1:n_perm 257 | 258 | %Permute the data 259 | if i == 1 260 | perm_data = int_res; 261 | else 262 | for s = 1:n_subs 263 | flat_perm_data(:, :, :, s) = flat_data(:, :, randperm(size(flat_data, 3)), s); 264 | end 265 | perm_data = reshape(flat_perm_data, size(int_res)); 266 | end 267 | 268 | %Calculate F at each time point and electrode combination 269 | 270 | %Calculate sums of squares 271 | SSyint = (sum(sum(sum(sum(perm_data, 3), 4), 5), 6).^2)/(n_conds_A*n_conds_B*n_conds_C*n_subs); 272 | %SSTO = sum(sum(sum(sum(perm_data.^2, 3), 4), 5), 6) - SSyint; 273 | SSA = sum(sum(sum(sum(perm_data, 4), 5), 6).^2, 3)/(n_conds_B*n_conds_C*n_subs) - SSyint; 274 | SSB = sum(sum(sum(sum(perm_data, 3), 5), 6).^2, 4)/(n_conds_A*n_conds_C*n_subs) - SSyint; 275 | SSC = sum(sum(sum(sum(perm_data, 3), 4), 6).^2, 5)/(n_conds_A*n_conds_B*n_subs) - SSyint; 276 | SSBL = sum(sum(sum(sum(perm_data, 3), 4), 5).^2, 6)/(n_conds_A*n_conds_B*n_conds_C) - SSyint; 277 | SSAxB = sum(sum(sum(sum(perm_data, 5), 6).^2, 3), 4)/(n_conds_C*n_subs) - SSA - SSB -SSyint; 278 | SSAxC = sum(sum(sum(sum(perm_data, 4), 6).^2, 3), 5)/(n_conds_B*n_subs) - SSA - SSC -SSyint; 279 | SSBxC = sum(sum(sum(sum(perm_data, 3), 6).^2, 4), 5)/(n_conds_A*n_subs) - SSB - SSC -SSyint; 280 | SSAxBxC = sum(sum(sum(sum(perm_data, 6).^2, 3), 4), 5)/n_subs - SSA - SSB - SSC - SSAxB - SSAxC - SSBxC - SSyint; 281 | SSAxBL = sum(sum(sum(sum(perm_data, 4), 5).^2, 3), 6)/(n_conds_B*n_conds_C) - SSA - SSBL - SSyint; 282 | SSBxBL = sum(sum(sum(sum(perm_data, 3), 5).^2, 4), 6)/(n_conds_A*n_conds_C) - SSB - SSBL - SSyint; 283 | SSCxBL = sum(sum(sum(sum(perm_data, 3), 4).^2, 5), 6)/(n_conds_A*n_conds_B) - SSC - SSBL - SSyint; 284 | SSAxBxBL = sum(sum(sum(sum(perm_data, 5).^2, 3), 4), 6)/n_conds_C - SSA - SSB - SSBL - SSAxB - SSAxBL - SSBxBL - SSyint; 285 | SSAxCxBL = sum(sum(sum(sum(perm_data, 4).^2, 3), 5), 6)/n_conds_B - SSA - SSC - SSBL - SSAxC - SSAxBL - SSCxBL - SSyint; 286 | SSBxCxBL = sum(sum(sum(sum(perm_data, 3).^2, 4), 5), 6)/n_conds_A - SSB - SSC - SSBL - SSBxC - SSBxBL - SSCxBL - SSyint; 287 | SSAxBxCxBL = sum(sum(sum(sum(perm_data.^2, 3), 4), 5), 6) - SSA - SSB - SSC - SSBL - SSAxB - SSAxC - SSBxC - SSAxBL - SSBxBL - SSCxBL - SSAxBxC - SSAxBxBL - SSAxCxBL - SSBxCxBL - SSyint; 288 | %SSRES = sum(sum(sum(sum(perm_data.^2, 3), 4), 5), 6) - SSA - SSB - SSC - SSBL - SSAxB - SSAxC - SSBxC - SSAxBxC - SSyint; 289 | 290 | %Doublechecking that the numbers match up 291 | %assert(all(SSRES - (SSAxBL + SSBxBL + SSCxBL + SSAxBxBL + SSAxCxBL + SSBxCxBL + SSAxBxCxBL) < 1e-9)); %SSRES is equal to its three subcomponents 292 | %assert(all(SSTO - (SSRES + SSBL + SSA + SSB + SSC + SSAxB + SSAxC + SSBxC + SSAxBxC) < 1e-9)); %sums of squares add up 293 | 294 | SSAxBxC(SSAxBxC < 1e-12) = 0; %Eliminates large F values that result from floating point error 295 | F_dist(i, :, :) = (SSAxBxC/dfAxBxC) ./ (SSAxBxCxBL/dfAxBxCerr); 296 | 297 | if VERBLEVEL 298 | if i == 1 && n_perm > 1 299 | fprintf('Permutations completed: ') 300 | elseif i == n_perm && n_perm > 1 301 | fprintf('%d\n', i) 302 | elseif ~mod(i, 1000) 303 | fprintf('%d, ', i) 304 | end 305 | end 306 | 307 | end 308 | 309 | %Extract unpermuted F-values 310 | F_obs = reshape(F_dist(1, :, :), [n_electrodes, n_time_pts]); 311 | 312 | %degrees of freedom 313 | df_effect = dfAxBxC; 314 | df_res = dfAxBxCerr; 315 | 316 | end 317 | -------------------------------------------------------------------------------- /perm_spANOVA.m: -------------------------------------------------------------------------------- 1 | %Calculate F-observed and the empirical F-distribution for an ANOVA with 2 | %one between subjects factor and up to two within subjects factors 3 | % 4 | %EXAMPLE USAGE 5 | % >> [F_obs, F_dist, df_effect, df_res, exact_test] = perm_spANOVA(data, [16, 16], [3, 4], 1e4) 6 | % 7 | %REQUIRED INPUTS 8 | % data - An electrode x time points x conditions x subjects array of ERP 9 | % data. Array will vary in number of dimensions based on how many 10 | % factors there are 11 | % cond_subs - Array giving the number of subjects in each condition of 12 | % the between subjects factor. For example, if cond_subs is 13 | % [8, 9], then there should be 17 subjects with first 8 14 | % being in condition A and the next 9 being in condition B 15 | % dims - Dimensions involved in the effect. Given the structure of 16 | % the data specified above, for a two-way ANOVA 3 indicates 17 | % the within-subjects factors, 4 indicates the between 18 | % subjects factors, and [3, 4] indicates the interaction 19 | % n_perm - Number of permutations to conduct 20 | % 21 | %OUTPUT 22 | % F_obs - electrode x time point matrix of unpermuted F-values 23 | % F_dist - F-values at each time point and electrode for each 24 | % permutation. 25 | % df_effect - numerator degrees of freedom 26 | % df_res - denominator degrees of freedom 27 | % exact_test - Boolean specifying whether the test was an exact test 28 | % 29 | % 30 | %VERSION DATE: 4 April 2019 31 | %AUTHOR: Eric Fields 32 | % 33 | %NOTE: This function is provided "as is" and any express or implied warranties 34 | %are disclaimed. 35 | 36 | %Copyright (c) 2017, Eric Fields 37 | %All rights reserved. 38 | %This code is free and open source software made available under the 3-clause BSD license. 39 | 40 | function [F_obs, F_dist, df_effect, df_res, exact_test] = perm_spANOVA(data, cond_subs, dims, n_perm, reduce) 41 | 42 | if ~all(cond_subs == cond_subs(1)) 43 | error('Split plot ANOVA is currently only available for designs with equal sample sizes'); 44 | end 45 | 46 | %Eliminate factors not involved in this effect and reduce interactions 47 | %via subtraction 48 | if nargin < 5 49 | reduce = true; 50 | end 51 | if reduce 52 | [reduced_data, new_dims] = reduce_data(data, dims); 53 | else 54 | reduced_data = data; 55 | new_dims = dims; 56 | end 57 | 58 | if ndims(reduced_data) == 3 && new_dims == 3 59 | [F_obs, F_dist, df_effect, df_res] = perm_crANOVA(reduced_data, cond_subs, n_perm); 60 | exact_test = true; 61 | elseif ndims(reduced_data) == 4 62 | [F_obs, F_dist, df_effect, df_res] = twoway(reduced_data, cond_subs, new_dims, n_perm); 63 | exact_test = false; 64 | elseif ndims(reduced_data) > 4 65 | [F_obs, F_dist, df_effect, df_res] = threeway(reduced_data, cond_subs, new_dims, n_perm); 66 | exact_test = false; 67 | end 68 | 69 | end 70 | 71 | 72 | function [F_obs, F_dist, df_effect, df_res] = twoway(data, cond_subs, dims, n_perm) 73 | 74 | global VERBLEVEL 75 | 76 | %Check array structure 77 | assert(ndims(data) == 4); 78 | 79 | %Some useful numbers 80 | [n_electrodes, n_time_pts, n_conds_B, n_subs] = size(data); 81 | n_conds_A = length(cond_subs); 82 | if sum(cond_subs) ~= n_subs 83 | error('The number of subjects in the ''cond_subs'' input doesn''t match the number of subjects in the data'); 84 | end 85 | 86 | %Interaction residuals 87 | if length(dims) == 2 88 | int_res = get_int_res(data, cond_subs, dims); 89 | end 90 | 91 | %Calculate degrees of freedom 92 | %(Always the same, so no point calculating in the loop) 93 | dfA = n_conds_A - 1; 94 | dfB = n_conds_B - 1; 95 | dfBL = n_subs - n_conds_A; 96 | dfAxB = dfA * dfB; 97 | dfBxBL = dfB * dfBL; 98 | 99 | %Perform n_perm permutations 100 | F_dist = NaN(n_perm, n_electrodes, n_time_pts); 101 | for i = 1:n_perm 102 | 103 | %Permute the data 104 | if length(dims) == 1 105 | if i == 1 106 | perm_data = data; 107 | elseif dims == 3 108 | for n = 1:n_subs 109 | perm_data(:, :, :, n) = data(:, :, randperm(size(data, 3)), n); 110 | end 111 | elseif dims == 4 112 | perm_data = data(:, :, :, randperm(size(data, 4))); 113 | end 114 | elseif length(dims) ==2 115 | if i ==1 116 | perm_data = int_res; 117 | else 118 | for n = 1:n_subs 119 | perm_data(:, :, :, n) = int_res(:, :, randperm(size(data, 3)), n); 120 | end 121 | perm_data = perm_data(:, :, :, randperm(size(data, 4))); 122 | end 123 | end 124 | 125 | %Calculate sums of squares 126 | A = 0; AS = 0; AB = 0; ABS = 0; 127 | for p = 1:n_conds_A 128 | first = sum(cond_subs(1:p)) - cond_subs(p) + 1; 129 | last = sum(cond_subs(1:p)); 130 | A = A + sum(sum(perm_data(:, :, :, first:last), 3), 4).^2 / (cond_subs(p) * n_conds_B); 131 | AS = AS + sum(sum(perm_data(:, :, :, first:last), 3).^2, 4) / n_conds_B; 132 | AB = AB + sum(sum(perm_data(:, :, :, first:last), 4).^2, 3) / cond_subs(p); 133 | ABS = ABS + sum(sum(perm_data(:, :, :, first:last).^2, 3), 4); 134 | end 135 | SSyint = (sum(sum(perm_data, 3), 4).^2) / (n_subs * n_conds_B); 136 | %SSTO = sum(sum(perm_data.^2, 3), 4) - SSyint; 137 | SSA = A - SSyint; 138 | SSB = sum(sum(perm_data, 4).^2, 3) / n_subs - SSyint; 139 | SSBL = AS - A; 140 | SSAxB = AB - A - SSB; 141 | SSBxBL = ABS - AB - AS + A; 142 | 143 | %Calculate F 144 | if length(dims) == 1 145 | if dims == 3 146 | SSB(SSB < 1e-12) = 0; %Eliminates large F values that result from floating point error 147 | F_dist(i, :, :) = (SSB/dfB) ./ (SSBxBL/dfBxBL); 148 | elseif dims == 4 149 | SSA(SSA < 1e-12) = 0; %Eliminates large F values that result from floating point error 150 | F_dist(i, :, :) = (SSA/dfA) ./ (SSBL/dfBL); 151 | end 152 | elseif length(dims) == 2 153 | SSAxB(SSAxB < 1e-12) = 0; %Eliminates large F values that result from floating point error 154 | F_dist(i, :, :) = (SSAxB/dfAxB) ./ (SSBxBL/dfBxBL); 155 | end 156 | 157 | %Report permutations completed to command window 158 | if VERBLEVEL 159 | if i == 1 && n_perm > 1 160 | fprintf('Permutations completed: ') 161 | elseif i == n_perm && n_perm > 1 162 | fprintf('%d\n', i) 163 | elseif ~mod(i, 1000) 164 | fprintf('%d, ', i) 165 | end 166 | end 167 | 168 | end 169 | 170 | %Extract unpermuted F-values 171 | F_obs = reshape(F_dist(1, :, :), [n_electrodes, n_time_pts]); 172 | 173 | %degrees of freedom and exact test 174 | if length(dims) == 1 175 | if dims == 3 176 | df_effect = dfB; 177 | df_res = dfBxBL; 178 | elseif dims == 4 179 | df_effect = dfA; 180 | df_res = dfBL; 181 | end 182 | elseif length(dims) == 2 183 | df_effect = dfAxB; 184 | df_res = dfBxBL; 185 | end 186 | 187 | end 188 | 189 | 190 | function [F_obs, F_dist, df_effect, df_res] = threeway(data, cond_subs, dims, n_perm) 191 | 192 | global VERBLEVEL 193 | 194 | %Check array structure 195 | assert(ndims(data) == 5); 196 | 197 | %Some useful numbers 198 | [n_electrodes, n_time_pts, n_conds_B, n_conds_C, n_subs] = size(data); 199 | n_conds_A = length(cond_subs); 200 | if sum(cond_subs) ~= n_subs 201 | error('The number of subjects in the ''cond_subs'' input doesn''t match the number of subjects in the data'); 202 | end 203 | 204 | %Interaction residuals 205 | int_res = get_int_res(data, cond_subs, dims); 206 | 207 | %Re-arrange data for within-subjects permutation 208 | flat_data = reshape(int_res, [n_electrodes, n_time_pts, n_conds_B*n_conds_C, n_subs]); 209 | 210 | %Calculate degrees of freedom 211 | %(Always the same, so no point calculating in the loop) 212 | dfA = n_conds_A - 1; 213 | dfB = n_conds_B - 1; 214 | dfC = n_conds_C - 1; 215 | dfBL = n_subs - n_conds_A; 216 | %dfAxB = dfA * dfB; 217 | %dfAxC = dfA * dfC; 218 | dfBxC = dfB * dfC; 219 | dfAxBxC = dfA * dfB * dfC; 220 | %dfBxBL = dfB * dfBL; 221 | %dfCxBL = dfC * dfBL; 222 | dfBxCxBL = dfB * dfC * dfBL; 223 | 224 | F_dist = NaN(n_perm, n_electrodes, n_time_pts); 225 | flat_perm_data = NaN(size(flat_data)); 226 | for i = 1:n_perm 227 | 228 | %Permute the data 229 | if i == 1 230 | perm_data = int_res; 231 | else 232 | for s = 1:n_subs 233 | flat_perm_data(:, :, :, s) = flat_data(:, :, randperm(size(flat_data, 3)), s); 234 | end 235 | perm_data = reshape(flat_perm_data, [n_electrodes, n_time_pts, n_conds_B, n_conds_C, n_subs]); 236 | if any(dims == 5) 237 | perm_data = perm_data(:, :, :, :, randperm(n_subs)); 238 | end 239 | end 240 | 241 | %Calculate sums of squares 242 | A = 0; AB = 0; AC = 0; AS = 0; ABS = 0; ACS = 0; ABC = 0; ABCS = 0; 243 | for p = 1:n_conds_A 244 | first = sum(cond_subs(1:p)) - cond_subs(p) + 1; 245 | last = sum(cond_subs(1:p)); 246 | A = A + sum(sum(sum(perm_data(:, :, :, :, first:last), 3), 4), 5).^2 / (cond_subs(p) * n_conds_B * n_conds_C); 247 | AB = AB + sum(sum(sum(perm_data(:, :, :, :, first:last), 4), 5).^2, 3) / (cond_subs(p) * n_conds_C); 248 | AC = AC + sum(sum(sum(perm_data(:, :, :, :, first:last), 3), 5).^2, 4) / (cond_subs(p) * n_conds_B); 249 | AS = AS + sum(sum(sum(perm_data(:, :, :, :, first:last), 3), 4).^2, 5) / (n_conds_B * n_conds_C); 250 | ABS = ABS + sum(sum(sum(perm_data(:, :, :, :, first:last), 4).^2, 3), 5) / n_conds_C; 251 | ACS = ACS + sum(sum(sum(perm_data(:, :, :, :, first:last), 3).^2, 4), 5) / n_conds_B; 252 | ABC = ABC + sum(sum(sum(perm_data(:, :, :, :, first:last), 5).^2, 3), 4) / cond_subs(p); 253 | ABCS = ABCS + sum(sum(sum(perm_data(:, :, :, :, first:last).^2, 3), 4), 5); 254 | end 255 | SSyint = sum(perm_data(:)).^2 / (n_subs * n_conds_B * n_conds_C); 256 | SSA = A - SSyint; 257 | SSB = sum(sum(sum(perm_data, 4), 5).^2, 3) / (n_subs * n_conds_C) - SSyint; 258 | SSC = sum(sum(sum(perm_data, 3), 5).^2, 4) / (n_subs * n_conds_B) - SSyint; 259 | %SSBL = AS - A; 260 | SSAxB = AB - SSA - SSB - SSyint; 261 | SSAxC = AC - SSA - SSC - SSyint; 262 | SSBxC = sum(sum(sum(perm_data, 5).^2, 3), 4) / n_subs - SSB - SSC - SSyint; 263 | %SSBxBL = ABS - AB - AS + A; 264 | %SSCxBL = ACS - AC - AS + A; 265 | SSAxBxC = ABC - SSAxB - SSAxC - SSBxC - SSA - SSB - SSC - SSyint; 266 | SSBxCxBL = ABCS - ABC - ABS - ACS + AB + AC + AS - A; 267 | 268 | %Calculate F 269 | if isequal(dims, [3, 4]) 270 | SSBxC(SSBxC < 1e-12) = 0; %Eliminates large F values that result from floating point error 271 | F_dist(i, :, :) = (SSBxC/dfBxC) ./ (SSBxCxBL/dfBxCxBL); 272 | elseif isequal(dims, [3, 4, 5]) 273 | SSAxBxC(SSAxBxC < 1e-12) = 0; %Eliminates large F values that result from floating point error 274 | F_dist(i, :, :) = (SSAxBxC/dfAxBxC) ./ (SSBxCxBL/dfBxCxBL); 275 | else 276 | error('Something has gone wrong! This design should have been reduced.'); 277 | end 278 | 279 | %Report permutations completed to command window 280 | if VERBLEVEL 281 | if i == 1 && n_perm > 1 282 | fprintf('Permutations completed: ') 283 | elseif i == n_perm && n_perm > 1 284 | fprintf('%d\n', i) 285 | elseif ~mod(i, 1000) 286 | fprintf('%d, ', i) 287 | end 288 | end 289 | 290 | end 291 | 292 | %Extract unpermuted F-values 293 | F_obs = reshape(F_dist(1, :, :), [n_electrodes, n_time_pts]); 294 | 295 | %degrees of freedom 296 | if isequal(dims, [3, 4]) 297 | df_effect = dfBxC; 298 | elseif isequal(dims, [3, 4, 5]) 299 | df_effect = dfAxBxC; 300 | end 301 | df_res = dfBxCxBL; 302 | 303 | end 304 | -------------------------------------------------------------------------------- /poi_library/dom4j-1.6.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericcfields/FMUT/f93e56a3ca3326248b281e9803152b185c809d21/poi_library/dom4j-1.6.1.jar -------------------------------------------------------------------------------- /poi_library/poi-3.8-20120326.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericcfields/FMUT/f93e56a3ca3326248b281e9803152b185c809d21/poi_library/poi-3.8-20120326.jar -------------------------------------------------------------------------------- /poi_library/poi-ooxml-3.8-20120326.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericcfields/FMUT/f93e56a3ca3326248b281e9803152b185c809d21/poi_library/poi-ooxml-3.8-20120326.jar -------------------------------------------------------------------------------- /poi_library/poi-ooxml-schemas-3.8-20120326.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericcfields/FMUT/f93e56a3ca3326248b281e9803152b185c809d21/poi_library/poi-ooxml-schemas-3.8-20120326.jar -------------------------------------------------------------------------------- /poi_library/stax-api-1.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericcfields/FMUT/f93e56a3ca3326248b281e9803152b185c809d21/poi_library/stax-api-1.0.1.jar -------------------------------------------------------------------------------- /poi_library/xmlbeans-2.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericcfields/FMUT/f93e56a3ca3326248b281e9803152b185c809d21/poi_library/xmlbeans-2.3.0.jar -------------------------------------------------------------------------------- /py_addpath.m: -------------------------------------------------------------------------------- 1 | %Add directory to import search path for the instance of 2 | %the Python interpreter currently controlled by MATLAB 3 | % 4 | %EXAMPLE USAGE 5 | % >> py_addpath('C:\Documents\ERPResults') 6 | % 7 | %REQUIRED INPUTS 8 | % directory - Directory to add the Python import search path 9 | % MATLAB_too - If true (or 1), directory will also be added to the 10 | % MATLAB path. {default: false} 11 | % 12 | %OPTIONAL OUTPUT 13 | % new_py_path - a cell array of the directories on the updated 14 | % Python path; to get this output without updating the 15 | % Python path, use an empty string as the input: 16 | % py_path = py_addpath('') 17 | % 18 | %VERSION DATE: 3 Novemeber 2017 19 | %AUTHOR: Eric Fields 20 | % 21 | %NOTE: This function is provided "as is" and any express or implied warranties 22 | %are disclaimed. 23 | 24 | %Copyright (c) 2017, Eric Fields 25 | %All rights reserved. 26 | %This code is free and open source software made available under the 3-clause BSD license. 27 | 28 | function new_py_path = py_addpath(directory, MATLAB_too) 29 | 30 | %check input 31 | if ~ischar(directory) 32 | error('Input must be a string') 33 | elseif ~exist(directory, 'dir') && ~isempty(directory) 34 | error('%s is not a valid directory', directory) 35 | end 36 | 37 | %Convert relative path to absolute path 38 | if ~isempty(directory) 39 | directory = char(py.os.path.abspath(directory)); 40 | end 41 | 42 | %add directory to Python path if not already present 43 | if ~any(strcmp(get_py_path(), directory)) 44 | py_path = py.sys.path; 45 | py_path.insert(int64(1), directory); 46 | end 47 | 48 | %add directory to MATLAB path if requested 49 | if nargin>1 && MATLAB_too 50 | addpath(directory); 51 | end 52 | 53 | %optionally return ammended path.sys as cell array 54 | if nargout 55 | new_py_path = get_py_path(); 56 | end 57 | 58 | end 59 | 60 | function current_py_path = get_py_path() 61 | %Function to return the current python search path as a cell array of strings 62 | current_py_path = cellfun(@char, cell(py.sys.path), 'UniformOutput', 0)'; 63 | end 64 | -------------------------------------------------------------------------------- /pyinstaller/fmut_pyinstaller.cmd: -------------------------------------------------------------------------------- 1 | REM Build pyinstaller frozen version of fmut.py 2 | 3 | cd C:\Users\ecfne\Documents\Eric\Coding\FMUT_development\FMUT 4 | pyinstaller -n py_fmut fmut.py 5 | -------------------------------------------------------------------------------- /pyinstaller/py_fmut_environment.yml: -------------------------------------------------------------------------------- 1 | name: root 2 | channels: 3 | - ecf 4 | - defaults 5 | dependencies: 6 | - _license=1.1=py36_1 7 | - alabaster=0.7.10=py36hcd07829_0 8 | - anaconda-client=1.6.5=py36hd36550c_0 9 | - anaconda=custom=py36h363777c_0 10 | - anaconda-navigator=1.6.9=py36hc720852_0 11 | - anaconda-project=0.8.0=py36h8b3bf89_0 12 | - asn1crypto=0.22.0=py36h8e79faa_1 13 | - astroid=1.5.3=py36h9d85297_0 14 | - astropy=2.0.2=py36h06391c4_4 15 | - babel=2.5.0=py36h35444c1_0 16 | - backports=1.0=py36h81696a8_1 17 | - backports.shutil_get_terminal_size=1.0.0=py36h79ab834_2 18 | - beautifulsoup4=4.6.0=py36hd4cc5e8_1 19 | - bitarray=0.8.1=py36h6af124b_0 20 | - bkcharts=0.2=py36h7e685f7_0 21 | - blaze=0.11.3=py36h8a29ca5_0 22 | - bleach=2.0.0=py36h0a7e3d6_0 23 | - bokeh=0.12.10=py36h0be3b39_0 24 | - boto=2.48.0=py36h1a776d2_1 25 | - bottleneck=1.2.1=py36hd119dfa_0 26 | - bzip2=1.0.6=haa5b126_2 27 | - ca-certificates=2017.08.26=h94faf87_0 28 | - cachecontrol=0.12.3=py36hfe50d7b_0 29 | - certifi=2017.7.27.1=py36h043bc9e_0 30 | - cffi=1.10.0=py36hae3d1b5_1 31 | - chardet=3.0.4=py36h420ce6e_1 32 | - click=6.7=py36hec8c647_0 33 | - cloudpickle=0.4.0=py36h639d8dc_0 34 | - clyent=1.2.2=py36hb10d595_1 35 | - colorama=0.3.9=py36h029ae33_0 36 | - comtypes=1.1.2=py36heb9b3d1_0 37 | - conda=4.3.30=py36h7e176b0_0 38 | - conda-env=2.6.0=h36134e3_1 39 | - console_shortcut=0.1.1=h6bb2dd7_3 40 | - contextlib2=0.5.5=py36he5d52c0_0 41 | - cryptography=2.0.3=py36h123decb_1 42 | - curl=7.55.1=h3b839b5_4 43 | - cycler=0.10.0=py36h009560c_0 44 | - cython=0.26.1=py36h18049ac_0 45 | - cytoolz=0.8.2=py36h547e66e_0 46 | - dask=0.15.4=py36hd19ff53_0 47 | - dask-core=0.15.4=py36h22c9028_0 48 | - datashape=0.5.4=py36h5770b85_0 49 | - decorator=4.1.2=py36he63a57b_0 50 | - distlib=0.2.5=py36h51371be_0 51 | - distributed=1.19.1=py36h8504682_0 52 | - docutils=0.14=py36h6012d8f_0 53 | - entrypoints=0.2.3=py36hfd66bb0_2 54 | - et_xmlfile=1.0.1=py36h3d2d736_0 55 | - fastcache=1.0.2=py36hffdae1b_0 56 | - filelock=2.0.12=py36hd7ddd41_0 57 | - flask=0.12.2=py36h98b5e8f_0 58 | - flask-cors=3.0.3=py36h8a3855d_0 59 | - freetype=2.8=h51f8f2c_1 60 | - get_terminal_size=1.0.0=h38e98db_0 61 | - gevent=1.2.2=py36h342a76c_0 62 | - glob2=0.5=py36h11cc1bd_1 63 | - greenlet=0.4.12=py36ha00ad21_0 64 | - h5py=2.7.0=py36hfbe0a52_1 65 | - hdf5=1.10.1=h98b8871_1 66 | - heapdict=1.0.0=py36h21fa5f4_0 67 | - html5lib=0.999999999=py36ha09b1f3_0 68 | - icc_rt=2017.0.4=h97af966_0 69 | - icu=58.2=ha66f8fd_1 70 | - idna=2.6=py36h148d497_1 71 | - imageio=2.2.0=py36had6c2d2_0 72 | - imagesize=0.7.1=py36he29f638_0 73 | - intel-openmp=2018.0.0=hcd89f80_7 74 | - ipykernel=4.6.1=py36hbb77b34_0 75 | - ipython=6.1.0=py36h236ecc8_1 76 | - ipython_genutils=0.2.0=py36h3c5d0ee_0 77 | - ipywidgets=7.0.0=py36h2e74ada_0 78 | - isort=4.2.15=py36h6198cc5_0 79 | - itsdangerous=0.24=py36hb6c5a24_1 80 | - jdcal=1.3=py36h64a5255_0 81 | - jedi=0.10.2=py36hed927a0_0 82 | - jinja2=2.9.6=py36h10aa3a0_1 83 | - jpeg=9b=hb83a4c4_2 84 | - jsonschema=2.6.0=py36h7636477_0 85 | - jupyter=1.0.0=py36h422fd7e_2 86 | - jupyter_client=5.1.0=py36h9902a9a_0 87 | - jupyter_console=5.2.0=py36h6d89b47_1 88 | - jupyter_core=4.3.0=py36h511e818_0 89 | - jupyterlab=0.27.0=py36h34cc53b_2 90 | - jupyterlab_launcher=0.4.0=py36h22c3ccf_0 91 | - lazy-object-proxy=1.3.1=py36hd1c21d2_0 92 | - libiconv=1.15=h1df5818_7 93 | - libpng=1.6.32=vc14h5163883_3 94 | - libssh2=1.8.0=vc14hcf584a9_2 95 | - libtiff=4.0.8=vc14h04e2a1e_10 96 | - libxml2=2.9.4=vc14h8fd0f11_5 97 | - libxslt=1.1.29=vc14hf85b8d4_5 98 | - llvmlite=0.20.0=py36_0 99 | - locket=0.2.0=py36hfed976d_1 100 | - lockfile=0.12.2=py36h0468280_0 101 | - lxml=4.1.0=py36h0dcd83c_0 102 | - lzo=2.10=vc14h0a64fa6_1 103 | - markupsafe=1.0=py36h0e26971_1 104 | - matplotlib=2.1.0=py36h11b4b9c_0 105 | - mccabe=0.6.1=py36hb41005a_1 106 | - menuinst=1.4.10=py36h42196fb_0 107 | - mistune=0.8.1=py36h007b88b_0 108 | - mkl=2018.0.0=h36b65af_4 109 | - mkl-service=1.1.2=py36h57e144c_4 110 | - mpmath=0.19=py36he326802_2 111 | - msgpack-python=0.4.8=py36h58b1e9d_0 112 | - multipledispatch=0.4.9=py36he44c36e_0 113 | - navigator-updater=0.1.0=py36h8a7b86b_0 114 | - nbconvert=5.3.1=py36h8dc0fde_0 115 | - nbformat=4.4.0=py36h3a5bc1b_0 116 | - networkx=2.0=py36hff991e3_0 117 | - nltk=3.2.4=py36hd0e0a39_0 118 | - nose=1.3.7=py36h1c3779e_2 119 | - notebook=5.2.1=py36h4fb2ca6_0 120 | - numba=0.35.0=np113py36_10 121 | - numexpr=2.6.2=py36h514de0f_2 122 | - numpy=1.13.3=py36ha320f96_0 123 | - numpydoc=0.7.0=py36ha25429e_0 124 | - odo=0.5.1=py36h7560279_0 125 | - olefile=0.44=py36h0a7bdd2_0 126 | - openpyxl=2.4.8=py36hf3b77f6_1 127 | - openssl=1.0.2m=h093b818_1 128 | - packaging=16.8=py36ha0986f6_1 129 | - pandas=0.21.0=py36he09d4dd_1 130 | - pandoc=1.19.2.1=hb2460c7_1 131 | - pandocfilters=1.4.2=py36h3ef6317_1 132 | - partd=0.3.8=py36hc8e763b_0 133 | - path.py=10.3.1=py36h3dd8b46_0 134 | - pathlib2=2.3.0=py36h7bfb78b_0 135 | - patsy=0.4.1=py36h42cefec_0 136 | - pep8=1.7.0=py36h0f3d67a_0 137 | - pickleshare=0.7.4=py36h9de030f_0 138 | - pillow=4.2.1=py36hdb25ab2_0 139 | - pip=9.0.1=py36h226ae91_4 140 | - pkginfo=1.4.1=py36hb0f9cfa_1 141 | - ply=3.10=py36h1211beb_0 142 | - progress=1.3=py36hbeca8d3_0 143 | - prompt_toolkit=1.0.15=py36h60b8f86_0 144 | - psutil=5.4.0=py36h4e662fb_0 145 | - py=1.4.34=py36ha4aca3a_1 146 | - pycodestyle=2.3.1=py36h7cc55cd_0 147 | - pycosat=0.6.3=py36h413d8a4_0 148 | - pycparser=2.18=py36hd053e01_1 149 | - pycrypto=2.6.1=py36he68e6e2_1 150 | - pycurl=7.43.0=py36h086bf4c_3 151 | - pyflakes=1.6.0=py36h0b975d6_0 152 | - pygments=2.2.0=py36hb010967_0 153 | - pylint=1.7.4=py36ha4e6ded_0 154 | - pyodbc=4.0.17=py36h0006bc2_0 155 | - pyopenssl=17.2.0=py36h15ca2fc_0 156 | - pyparsing=2.2.0=py36h785a196_1 157 | - pyqt=5.6.0=py36hb5ed885_5 158 | - pysocks=1.6.7=py36h698d350_1 159 | - pytables=3.4.2=py36h71138e3_2 160 | - pytest=3.2.1=py36h753b05e_1 161 | - python=3.6.3=h3b118a2_4 162 | - python-dateutil=2.6.1=py36h509ddcb_1 163 | - pytz=2017.2=py36h05d413f_1 164 | - pywavelets=0.5.2=py36hc649158_0 165 | - pywin32=221=py36h9c10281_0 166 | - pyyaml=3.12=py36h1d1928f_1 167 | - pyzmq=16.0.2=py36h38c27d9_2 168 | - qt=5.6.2=vc14h6f8c307_12 169 | - qtawesome=0.4.4=py36h5aa48f6_0 170 | - qtconsole=4.3.1=py36h99a29a9_0 171 | - qtpy=1.3.1=py36hb8717c5_0 172 | - requests=2.18.4=py36h4371aae_1 173 | - rope=0.10.5=py36hcaf5641_0 174 | - ruamel_yaml=0.11.14=py36h9b16331_2 175 | - scikit-image=0.13.0=py36h6dffa3f_1 176 | - scikit-learn=0.19.1=py36h53aea1b_0 177 | - scipy=1.0.0=py36h1260518_0 178 | - seaborn=0.8.0=py36h62cb67c_0 179 | - setuptools=36.5.0=py36h65f9e6e_0 180 | - simplegeneric=0.8.1=py36heab741f_0 181 | - singledispatch=3.4.0.3=py36h17d0c80_0 182 | - sip=4.18.1=py36h9c25514_2 183 | - six=1.11.0=py36h4db2310_1 184 | - snowballstemmer=1.2.1=py36h763602f_0 185 | - sortedcollections=0.5.3=py36hbefa0ab_0 186 | - sortedcontainers=1.5.7=py36ha90ac20_0 187 | - sphinx=1.6.3=py36h9bb690b_0 188 | - sphinxcontrib=1.0=py36hbbac3d2_1 189 | - sphinxcontrib-websupport=1.0.1=py36hb5e5916_1 190 | - spyder=3.2.4=py36h8845eaa_0 191 | - sqlalchemy=1.1.13=py36h5948d12_0 192 | - sqlite=3.20.1=h9eeafa9_2 193 | - statsmodels=0.8.0=py36h6189b4c_0 194 | - sympy=1.1.1=py36h96708e0_0 195 | - tblib=1.3.2=py36h30f5020_0 196 | - testpath=0.3.1=py36h2698cfe_0 197 | - tk=8.6.7=hcb92d03_3 198 | - toolz=0.8.2=py36he152a52_0 199 | - tornado=4.5.2=py36h57f6048_0 200 | - traitlets=4.3.2=py36h096827d_0 201 | - typing=3.6.2=py36hb035bda_0 202 | - unicodecsv=0.14.1=py36h6450c06_0 203 | - urllib3=1.22=py36h276f60a_0 204 | - vc=14=h2379b0c_2 205 | - vs2015_runtime=14.0.25123=hd4c4e62_2 206 | - wcwidth=0.1.7=py36h3d5aa90_0 207 | - webencodings=0.5.1=py36h67c50ae_1 208 | - werkzeug=0.12.2=py36h866a736_0 209 | - wheel=0.29.0=py36h6ce6cde_1 210 | - widgetsnbextension=3.0.2=py36h364476f_1 211 | - win_inet_pton=1.0.1=py36he67d7fd_1 212 | - win_unicode_console=0.5=py36hcdbd4b5_0 213 | - wincertstore=0.2=py36h7fe50ca_0 214 | - wrapt=1.10.11=py36he5f5981_0 215 | - xlrd=1.1.0=py36h1cb58dc_1 216 | - xlsxwriter=1.0.2=py36hf723b7d_0 217 | - xlwings=0.11.4=py36hd3cf94d_0 218 | - xlwt=1.3.0=py36h1a4751e_0 219 | - yaml=0.1.7=hc54c509_2 220 | - zict=0.1.3=py36h2d8e73e_0 221 | - zlib=1.2.11=h8395fce_2 222 | - pytictoc=1.4.0=py36_0 223 | - pip: 224 | - altgraph==0.14 225 | - backports.shutil-get-terminal-size==1.0.0 226 | - et-xmlfile==1.0.1 227 | - future==0.16.0 228 | - ipython-genutils==0.2.0 229 | - jupyter-client==5.1.0 230 | - jupyter-console==5.2.0 231 | - jupyter-core==4.3.0 232 | - jupyterlab-launcher==0.4.0 233 | - macholib==1.8 234 | - matlabengineforpython==R2017b 235 | - pefile==2017.11.5 236 | - prompt-toolkit==1.0.15 237 | - pyinstaller==3.3 238 | - ruamel-yaml==0.11.14 239 | - tables==3.4.2 240 | - win-inet-pton==1.0.1 241 | - win-unicode-console==0.5 242 | prefix: C:\Anaconda3 243 | 244 | -------------------------------------------------------------------------------- /reduce_data.m: -------------------------------------------------------------------------------- 1 | %Reduce data to the simplest design that can calculate an equivalent 2 | %effect. For example, for a 3 x 2 design, the main effects can be 3 | %calculated by averaging across the other factor and then conducting a 4 | %one-way ANOVA. Similarly, the interaction effect an be calculated by 5 | %subtracting across the factor with two levels and then conducting a 6 | %one-way ANOVA. 7 | % 8 | %reduce_data performs two computations: 9 | %1. Average across any factors not included in the effect to be calculated 10 | % (as specified by the dims input) 11 | %2. Subtract across any factors involved in an interaction that have only 12 | % two levels (unless all factors have two levels, in which case subtract 13 | % across all but one factor) 14 | % 15 | %EXAMPLE USAGE 16 | % >> [reduced_data, new_dims] = reduce_data(data, [3, 4]) 17 | % 18 | %REQUIRED INPUTS 19 | % data - An electrode x time points x conditions x subjects array of ERP 20 | % data. Array will vary in number of dimensions based on how many 21 | % factors there are 22 | % dims - Dimensions of the data array involved in the effect to be 23 | % calculated. For example, if data is an electrode x time points 24 | % x Factor A x Factor B x subjects array and you want to 25 | % calculated the main effect of A, dims = 3. If you want to 26 | % calculate the AxB interaciton, dims = [3, 4]. 27 | % 28 | %OUTPUT 29 | % reduced_data - data reduced for analysis 30 | % new_dims - the dimensions of reduced_data that are involved in the 31 | % effect 32 | % 33 | %VERSION DATE: 13 July 2017 34 | %AUTHOR: Eric Fields 35 | % 36 | %NOTE: This function is provided "as is" and any express or implied warranties 37 | %are disclaimed. 38 | 39 | %Copyright (c) 2017, Eric Fields 40 | %All rights reserved. 41 | %This code is free and open source software made available under the 3-clause BSD license. 42 | 43 | 44 | function [reduced_data, new_dims] = reduce_data(data, dims) 45 | 46 | %Can't reduce across betwee-subjects factors, so extract just the 47 | %within-subject factors 48 | wdims = dims(dims ~= ndims(data)); 49 | 50 | n_electrodes = size(data, 1); 51 | n_time_pts = size(data, 2); 52 | n_subs = size(data, ndims(data)); 53 | 54 | %Determine if betwee-subjects factors are involved in the effect 55 | bg = any(dims == ndims(data)); 56 | 57 | 58 | %% Average across factors not involved in this effect 59 | 60 | if (length(wdims) < ndims(data) - 3) || isempty(wdims) 61 | %Put the factors to average across as the initial dimensions 62 | num_dims = ndims(data); 63 | all_dims = 1:ndims(data); 64 | reorder = [all_dims(~ismember(all_dims, [1,2,wdims,num_dims])), all_dims(ismember(all_dims, [1,2,wdims,num_dims]))]; 65 | reduced_data = permute(data, reorder); 66 | %Reduce all the factors to average across to a single dimension 67 | dim_sizes = size(reduced_data); 68 | num_dims_to_avg = ndims(data) - length(wdims) - 3; 69 | reduced_data = reshape(reduced_data, [prod(dim_sizes(1:num_dims_to_avg)), dim_sizes((num_dims_to_avg+1):end)]); 70 | %Take the mean across that dimension 71 | reduced_data = mean(reduced_data, 1); 72 | %Get rid of the extra singleton dimension 73 | reduced_data = reshape(reduced_data, dim_sizes((num_dims_to_avg+1):end)); 74 | else 75 | reduced_data = data; 76 | end 77 | 78 | %% For interactions, subtract across factors with two levels 79 | 80 | dim_sizes = size(reduced_data); 81 | factor_levels = dim_sizes(3:(ndims(reduced_data)-1)); 82 | if length(dims)>1 && sum(factor_levels==2) 83 | %Put the factors to subtract across as the initial dimensions 84 | if sum(factor_levels>2) 85 | reorder = [find(factor_levels==2)+2, 1, 2, find(factor_levels~=2)+2, ndims(reduced_data)]; 86 | elseif bg 87 | reorder = [3:(ndims(reduced_data)-1), 1, 2, ndims(reduced_data)]; 88 | else 89 | reorder = [4:(ndims(reduced_data)-1), 1, 2, 3, ndims(reduced_data)]; 90 | end 91 | reduced_data = permute(reduced_data, reorder); 92 | %Flatten data and subtract until the data is reduced to the right size 93 | reduced_data = reshape(reduced_data, 1, []); 94 | if sum(factor_levels>2) 95 | end_size = n_electrodes * n_time_pts * prod(factor_levels(factor_levels~=2)) * n_subs; 96 | elseif bg 97 | end_size = n_electrodes * n_time_pts * n_subs; 98 | else 99 | end_size = n_electrodes * n_time_pts * 2 * n_subs; 100 | end 101 | while length(reduced_data) > end_size 102 | reduced_data = reduced_data(1:2:length(reduced_data)) - reduced_data(2:2:length(reduced_data)); 103 | end 104 | %Put back in regular format 105 | if sum(factor_levels>2) 106 | reduced_data = reshape(reduced_data, [n_electrodes, n_time_pts, factor_levels(factor_levels~=2), size(data,ndims(data))]); 107 | elseif bg 108 | reduced_data = reshape(reduced_data, [n_electrodes, n_time_pts, n_subs]); 109 | else 110 | reduced_data = reshape(reduced_data, [n_electrodes, n_time_pts, 2, n_subs]); 111 | end 112 | end 113 | 114 | %% Dimensions of reduced_data involved in effect 115 | 116 | new_dims = (3:ndims(reduced_data)-1); 117 | if bg 118 | new_dims = [new_dims ndims(reduced_data)]; 119 | end 120 | 121 | end 122 | 123 | -------------------------------------------------------------------------------- /report_results.m: -------------------------------------------------------------------------------- 1 | %Summarize results of F-test at the command window 2 | % 3 | %EXAMPLE USAGE 4 | % >> report_results(GND, 1) 5 | % 6 | %REQUIRED INPUTS 7 | % GND - A GND or GRP variable with F-test results 8 | % test_id - The test number of the results to report within the GND or 9 | % GRP variable 10 | % 11 | %VERSION DATE: 14 July 2017 12 | %AUTHOR: Eric Fields 13 | % 14 | %NOTE: This function is provided "as is" and any express or implied warranties 15 | %are disclaimed. 16 | 17 | %Copyright (c) 2017, Eric Fields 18 | %All rights reserved. 19 | %This code is free and open source software made available under the 3-clause BSD license. 20 | 21 | function report_results(GND, test_id) 22 | 23 | switch GND.F_tests(test_id).mult_comp_method 24 | case 'Fmax perm test' 25 | report_Fmax(GND, test_id) 26 | case 'cluster mass perm test' 27 | report_clust(GND, test_id) 28 | otherwise 29 | if any(strcmpi(GND.F_tests(test_id), {'bh', 'by', 'bky'})) 30 | report_fdr(GND, test_id) 31 | end 32 | end 33 | 34 | end 35 | 36 | 37 | function report_Fmax(GND, test_id) 38 | 39 | results = GND.F_tests(test_id); 40 | [effects, effects_labels] = get_effects(results.factors); 41 | if isstruct(results.F_obs) 42 | assert(all(strcmpi(fieldnames(results.F_obs), effects_labels))) 43 | end 44 | 45 | fprintf('\n##### RESULTS #####\n'); 46 | for i = 1:length(effects) 47 | fprintf('\n%s effect\n', effects_labels{i}); 48 | if length(effects) == 1 49 | if any(results.null_test(:)) 50 | fprintf('Critical F-value: %.3f\n', results.F_crit); 51 | fprintf('That corresponds to a test-wise alpha level of %.3f.\n', ... 52 | fcdf(results.F_crit, results.df(1), results.df(2), 'upper')); 53 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 54 | for t = 1:size(results.time_wind, 1) 55 | fprintf('Significant electrodes for time window %d - %d: ', results.time_wind(t, 1), results.time_wind(t, 2)); 56 | fprintf('%s ', results.include_chans{results.null_test(:, t)}); 57 | fprintf('\n'); 58 | end 59 | else 60 | fprintf('Electrodes and time points with significant effects:\n'); 61 | for t = 1:length(results.used_tpt_ids) 62 | if any(results.null_test(:, t)) 63 | fprintf('%d ms, electrode(s): ', GND.time_pts(results.used_tpt_ids(t))); 64 | fprintf('%s ', results.include_chans{results.null_test(:, t)}); 65 | fprintf('\n'); 66 | end 67 | end 68 | end 69 | fprintf('All significant corrected p-values are between %f and %f.\n', ... 70 | max(results.adj_pval(results.adj_pval <= .05)), ... 71 | min(results.adj_pval(:))); 72 | else 73 | fprintf('NO significant time points or electrodes.\n'); 74 | end 75 | else 76 | if any(results.null_test.(effects_labels{i})(:)) 77 | fprintf('Critical F-value: %.3f\n', results.F_crit.(effects_labels{i})); 78 | fprintf('That corresponds to a test-wise alpha level of %.3f.\n', ... 79 | fcdf(results.F_crit.(effects_labels{i}), results.df.(effects_labels{i})(1), results.df.(effects_labels{i})(2), 'upper')); 80 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 81 | for t = 1:size(results.time_wind, 1) 82 | fprintf('Significant electrodes for time windonw %d - %d: ', results.time_wind(t, 1), results.time_wind(t, 2)); 83 | fprintf('%s ', results.include_chans{results.null_test.(effects_labels{i})(:, t)}); 84 | fprintf('\n'); 85 | end 86 | else 87 | fprintf('Electrodes and time points with significant effects:\n'); 88 | for t = 1:length(results.used_tpt_ids) 89 | if any(results.null_test.(effects_labels{i})(:, t)) 90 | fprintf('%d ms, electrode(s): ', GND.time_pts(results.used_tpt_ids(t))); 91 | fprintf('%s ', results.include_chans{results.null_test.(effects_labels{i})(:, t)}); 92 | fprintf('\n'); 93 | end 94 | end 95 | end 96 | fprintf('All significant corrected p-values are between %f and %f.\n', ... 97 | max(results.adj_pval.(effects_labels{i})(results.adj_pval.(effects_labels{i}) <= .05)), ... 98 | min(results.adj_pval.(effects_labels{i})(:))); 99 | else 100 | fprintf('NO significant time points or electrodes.\n'); 101 | end 102 | end 103 | end 104 | 105 | end 106 | 107 | 108 | function report_clust(GND, test_id) 109 | 110 | results = GND.F_tests(test_id); 111 | [effects, effects_labels] = get_effects(results.factors); 112 | if isstruct(results.F_obs) 113 | assert(all(strcmpi(fieldnames(results.F_obs), effects_labels))) 114 | end 115 | 116 | fprintf('\n##### RESULTS #####\n\n'); 117 | if length(effects) == 1 118 | fprintf('%s effect\n', effects_labels{1}); 119 | fprintf('# of clusters found: %d\n', length(results.clust_info.null_test)); 120 | fprintf('# of significant clusters found: %d\n', sum(results.clust_info.null_test)); 121 | if sum(results.clust_info.null_test) 122 | fprintf('Significant cluster p-values range from %f to %f.\n', ... 123 | max(results.clust_info.pval(results.clust_info.null_test)), ... 124 | min(results.clust_info.pval(results.clust_info.null_test))); 125 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 126 | for t = 1:size(results.time_wind, 1) 127 | fprintf('Electrodes in a significant cluster for time window %d - %d: ', results.time_wind(t, 1), results.time_wind(t, 2)); 128 | fprintf('%s ', results.include_chans{results.null_test(:, t)}); 129 | fprintf('\n'); 130 | end 131 | fprintf('\n'); 132 | else 133 | fprintf('Electrodes and time points included in a significant cluster:\n'); 134 | for t = 1:length(results.used_tpt_ids) 135 | if any(results.null_test(:, t)) 136 | fprintf('%d ms, electrode(s): ', GND.time_pts(results.used_tpt_ids(t))); 137 | fprintf('%s ', results.include_chans{results.null_test(:, t)}); 138 | fprintf('\n'); 139 | end 140 | end 141 | fprintf('\n'); 142 | end 143 | else 144 | fprintf('All p-values >= %f\n\n', min(results.clust_info.pval)); 145 | end 146 | else 147 | for i = 1:length(effects) 148 | fprintf('%s effect\n', effects_labels{i}); 149 | fprintf('# of clusters found: %d\n', length(results.clust_info.(effects_labels{i}).null_test)); 150 | fprintf('# of significant clusters found: %d\n', sum(results.clust_info.(effects_labels{i}).null_test)); 151 | if sum(results.clust_info.(effects_labels{i}).null_test) 152 | fprintf('Significant cluster p-values range from %f to %f.\n', ... 153 | max(results.clust_info.(effects_labels{i}).pval(results.clust_info.(effects_labels{i}).null_test)), ... 154 | min(results.clust_info.(effects_labels{i}).pval(results.clust_info.(effects_labels{i}).null_test))); 155 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 156 | for t = 1:size(results.time_wind, 1) 157 | fprintf('Electrodes in a significant cluster for time window %d - %d: ', results.time_wind(t, 1), results.time_wind(t, 2)); 158 | fprintf('%s ', results.include_chans{results.null_test.(effects_labels{i})(:, t)}); 159 | fprintf('\n'); 160 | end 161 | fprintf('\n'); 162 | else 163 | fprintf('Electrodes and time points included in a significant cluster:\n'); 164 | for t = 1:length(results.used_tpt_ids) 165 | if any(results.null_test.(effects_labels{i})(:, t)) 166 | fprintf('%d ms, electrode(s): ', GND.time_pts(results.used_tpt_ids(t))); 167 | fprintf('%s ', results.include_chans{results.null_test.(effects_labels{i})(:, t)}); 168 | fprintf('\n'); 169 | end 170 | end 171 | fprintf('\n'); 172 | end 173 | else 174 | fprintf('All p-values >= %f.\n\n', min(results.clust_info.(effects_labels{i}).pval)); 175 | end 176 | end 177 | end 178 | 179 | end 180 | 181 | 182 | function report_fdr(GND, test_id) 183 | 184 | results = GND.F_tests(test_id); 185 | [effects, effects_labels] = get_effects(results.factors); 186 | if isstruct(results.F_obs) 187 | assert(all(strcmpi(fieldnames(results.F_obs), effects_labels))) 188 | end 189 | 190 | fprintf('\n##### RESULTS #####\n'); 191 | if length(effects) == 1 192 | fprintf('\n%s effect\n', effects_labels{1}); 193 | if any(results.null_test(:)) 194 | fprintf('Critical F-value: %.3f\n', results.F_crit); 195 | fprintf('That corresponds to a test-wise alpha level of %.3f.\n', ... 196 | fcdf(results.F_crit, results.df(1), results.df(2), 'upper')); 197 | fprintf('Total number of significant differences: %d\n', sum(results.null_test(:))); 198 | fprintf('Estimated upper bound on expected number of false discoveries: %.1f.\n', sum(results.null_test(:))*q); 199 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 200 | for t = 1:size(results.time_wind, 1) 201 | fprintf('Significant electrodes for time window %d - %d: ', results.time_wind(t, 1), results.time_wind(t, 2)); 202 | fprintf('%s ', results.include_chans{results.null_test(:, t)}); 203 | fprintf('\n'); 204 | end 205 | else 206 | fprintf('Electrodes and time points with significant effects:\n'); 207 | for t = 1:length(results.used_tpt_ids) 208 | if any(results.null_test(:, t)) 209 | fprintf('%d ms, electrode(s): ', GND.time_pts(results.used_tpt_ids(t))); 210 | fprintf('%s ', results.include_chans{results.null_test(:, t)}); 211 | fprintf('\n'); 212 | end 213 | end 214 | end 215 | if ~strcmpi(method, 'bky') 216 | fprintf('All significant corrected p-values are between %f and %f.\n', ... 217 | max(results.adj_pval(results.adj_pval <= .05)), ... 218 | min(results.adj_pval(:))); 219 | end 220 | else 221 | fprintf('NO significant time points or electrodes.\n\n'); 222 | end 223 | else 224 | for i = 1:length(effects) 225 | fprintf('\n%s effect\n', effects_labels{i}); 226 | if any(results.null_test.(effects_labels{i})(:)) 227 | fprintf('Critical F-value: %.3f\n', results.F_crit.(effects_labels{i})); 228 | fprintf('That corresponds to a test-wise alpha level of %.3f.\n', ... 229 | fcdf(results.F_crit.(effects_labels{i}), results.df.(effects_labels{i})(1), results.df.(effects_labels{i})(2), 'upper')); 230 | fprintf('Total number of significant differences: %d\n', sum(results.null_test.(effects_labels{i})(:))); 231 | fprintf('Estimated upper bound on expected number of false discoveries: %.1f\n', sum(results.null_test.(effects_labels{i})(:))*q); 232 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 233 | for t = 1:size(results.time_wind, 1) 234 | fprintf('Significant electrodes for time windonw %d - %d: ', results.time_wind(t, 1), results.time_wind(t, 2)); 235 | fprintf('%s ', results.include_chans{results.null_test.(effects_labels{i})(:, t)}); 236 | fprintf('\n'); 237 | end 238 | else 239 | fprintf('Electrodes and time points with significant effects:\n'); 240 | for t = 1:length(results.used_tpt_ids) 241 | if any(results.null_test.(effects_labels{i})(:, t)) 242 | fprintf('%d ms, electrode(s): ', GND.time_pts(results.used_tpt_ids(t))); 243 | fprintf('%s ', results.include_chans{results.null_test.(effects_labels{i})(:, t)}); 244 | fprintf('\n'); 245 | end 246 | end 247 | end 248 | if ~strcmpi(method, 'bky') 249 | fprintf('All significant corrected p-values are between %f and %f.\n', ... 250 | max(results.adj_pval.(effects_labels{i})(results.adj_pval.(effects_labels{i}) <= .05)), ... 251 | min(results.adj_pval.(effects_labels{i})(:))); 252 | end 253 | else 254 | fprintf('NO significant time points or electrodes.\n\n'); 255 | end 256 | end 257 | end 258 | 259 | end 260 | -------------------------------------------------------------------------------- /testing/A_setupTest.m: -------------------------------------------------------------------------------- 1 | %FMUT unit testing 2 | %General setup 3 | % 4 | %AUTHOR: Eric Fields 5 | %VERSION DATE: 12 June 2020 6 | 7 | clear all; close all; path(pathdef); %#ok 8 | 9 | %Add EEGLAB and MUT to path 10 | [ALLEEG, EEG, CURRENTSET, ALLCOM] = eeglab; 11 | 12 | %Remove FMUT directory within EEGLAB plugins folder 13 | rmpath(fileparts(which('FclustGND'))); 14 | 15 | %Add FMUT folder to search path 16 | FMUT_dir = fileparts(fileparts(which('A_setupTest'))); 17 | addpath(FMUT_dir); 18 | 19 | %Clear everything before testing 20 | close all; 21 | clear all; %#ok 22 | 23 | global test_xls_output; 24 | test_xls_output = true; 25 | 26 | %Clear spreadsheet outputs folder 27 | if test_xls_output 28 | FMUT_dir = fileparts(fileparts(mfilename('fullpath'))); 29 | cd(fullfile(FMUT_dir, 'testing', 'outputs')); 30 | delete *.xlsx 31 | cd(fullfile(FMUT_dir, 'testing')); 32 | end 33 | 34 | %Make sure we are using the right FMUT functions 35 | assert(strcmp(fileparts(which('FclustGND')), FMUT_dir)); 36 | -------------------------------------------------------------------------------- /testing/B_FmaxGNDTest.m: -------------------------------------------------------------------------------- 1 | %Test FmaxGND function 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 12 June 2020 5 | 6 | global test_xls_output; 7 | if isempty(test_xls_output) 8 | test_xls_output = true; 9 | end 10 | 11 | %Load a GND for testing 12 | FMUT_dir = fileparts(fileparts(which('B_FmaxGNDTest'))); 13 | load(fullfile(FMUT_dir, 'testing', 'data', 'EmProb_13subs_Test.GND'), '-mat'); 14 | 15 | %Define some general variables 16 | time_wind = [500, 800]; 17 | include_chans = {'Cz', 'CPz', 'Pz'}; 18 | [~, start_sample] = min(abs( GND.time_pts - time_wind(1) )); 19 | [~, end_sample ] = min(abs( GND.time_pts - time_wind(2) )); 20 | electrodes = NaN(1, length(include_chans)); 21 | for c = 1:length(include_chans) 22 | electrodes(c) = find(strcmp(include_chans(c), {GND.chanlocs.labels})); 23 | end 24 | n_perm = 100; 25 | 26 | %% Exact interaction 27 | 28 | if test_xls_output 29 | output_file = fullfile('outputs', 'Fmax_test.xlsx'); 30 | else 31 | output_file = false; 32 | end 33 | GND = FmaxGND(GND, ... 34 | 'bins', [24, 26, 27, 29], ... 35 | 'factor_names', {'Probability', 'Emotion'}, ... 36 | 'factor_levels', [2, 2], ... 37 | 'time_wind', time_wind, ... 38 | 'include_chans', include_chans, ... 39 | 'n_perm', 1e4, ... 40 | 'alpha', 0.05, ... 41 | 'save_GND', 'no', ... 42 | 'output_file', output_file, ... 43 | 'plot_raster', 'yes'); 44 | 45 | %F==t^2 46 | assert(all(all(GND.F_tests(end).F_obs.ProbabilityXEmotion - GND.grands_t(electrodes, start_sample:end_sample, 51).^2 < 1e-4))); 47 | 48 | %Check p-values with t-test 49 | GND = tmaxGND(GND, 51, ... 50 | 'time_wind', time_wind, ... 51 | 'include_chans', include_chans, ... 52 | 'n_perm', 1e4, ... 53 | 'alpha', 0.05, ... 54 | 'save_GND', 'no', ... 55 | 'plot_gui', 'no', ... 56 | 'plot_raster', 'no', ... 57 | 'verblevel', 2); 58 | if test_xls_output 59 | ttest2xls(GND, length(GND.t_tests), fullfile('outputs', 'ttest2xls_test.xlsx')) 60 | end 61 | 62 | 63 | %Critical F == critical t^2 64 | assert(GND.F_tests(end).F_crit.ProbabilityXEmotion - GND.t_tests(end).crit_t(1)^2 < 1); 65 | %p-values are approximately the same 66 | assert(all(all(GND.F_tests(end).adj_pval.ProbabilityXEmotion - GND.t_tests(end).adj_pval < .05))) 67 | 68 | %Reproduce test 69 | GND = FmaxGND(GND, ... 70 | 'bins', [24, 26, 27, 29], ... 71 | 'factor_names', {'Probability', 'Emotion'}, ... 72 | 'factor_levels', [2, 2], ... 73 | 'time_wind', time_wind, ... 74 | 'include_chans', include_chans, ... 75 | 'n_perm', n_perm, ... 76 | 'alpha', 0.05, ... 77 | 'save_GND', 'no', ... 78 | 'plot_raster', 'no', ... 79 | 'reproduce_test', 1); 80 | 81 | 82 | %% Oneway ANOVA 83 | 84 | if test_xls_output 85 | output_file = fullfile('outputs', 'Fmax_test_oneway.xlsx'); 86 | else 87 | output_file = false; 88 | end 89 | GND = FmaxGND(GND, ... 90 | 'bins', [24, 26, 27], ... 91 | 'factor_names', {'NEU_Probability'}, ... 92 | 'factor_levels', 3, ... 93 | 'time_wind', time_wind, ... 94 | 'include_chans', include_chans, ... 95 | 'n_perm', n_perm, ... 96 | 'alpha', 0.05, ... 97 | 'save_GND', 'no', ... 98 | 'output_file', output_file, ... 99 | 'plot_raster', 'yes'); 100 | 101 | 102 | %% Approximate interaction 103 | 104 | GND = FmaxGND(GND, ... 105 | 'bins', [24:29, 31, 32, 33], ... 106 | 'factor_names', {'Probability', 'Emotion'}, ... 107 | 'factor_levels', [3, 3], ... 108 | 'time_wind', time_wind, ... 109 | 'include_chans', include_chans, ... 110 | 'n_perm', n_perm, ... 111 | 'alpha', 0.05, ... 112 | 'save_GND', 'no', ... 113 | 'plot_raster', 'no'); 114 | 115 | 116 | %% Mean time window 117 | 118 | if test_xls_output 119 | output_file = fullfile('outputs', 'Fmax_meanwind_test.xlsx'); 120 | else 121 | output_file = false; 122 | end 123 | %F-test 124 | GND = FmaxGND(GND, ... 125 | 'bins', [24, 26, 27, 29], ... 126 | 'factor_names', {'Probability', 'Emotion'}, ... 127 | 'factor_levels', [2, 2], ... 128 | 'time_wind', [300 400; 500 800], ... 129 | 'mean_wind', 'yes', ... 130 | 'include_chans', include_chans, ... 131 | 'n_perm', n_perm, ... 132 | 'alpha', 0.05, ... 133 | 'save_GND', 'no', ... 134 | 'plot_raster', 'yes', ... 135 | 'output_file', output_file); 136 | 137 | %Calculate t-test on same data 138 | GND = tmaxGND(GND, 51, ... 139 | 'n_perm', n_perm, ... 140 | 'time_wind', [300 400; 500 800], ... 141 | 'mean_wind', 'yes', ... 142 | 'include_chans', include_chans, ... 143 | 'plot_raster', 'no', ... 144 | 'plot_gui', 'no', ... 145 | 'plot_mn_topo', 'no', ... 146 | 'save_GND', 'no', ... 147 | 'verblevel', 2); 148 | 149 | %F==t^2 150 | assert(all(all(GND.F_tests(end).F_obs.ProbabilityXEmotion - GND.t_tests(end).data_t.^2 < 1e-9))) 151 | -------------------------------------------------------------------------------- /testing/C_FclustGNDTest.m: -------------------------------------------------------------------------------- 1 | %Test FclustGND function 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 12 June 2020 5 | 6 | global test_xls_output; 7 | if isempty(test_xls_output) 8 | test_xls_output = true; 9 | end 10 | 11 | %Load a GND for testing 12 | %Load a GND for testing 13 | FMUT_dir = fileparts(fileparts(which('C_FclustGNDTest'))); 14 | load(fullfile(FMUT_dir, 'testing', 'data', 'EmProb_13subs_Test.GND'), '-mat'); 15 | 16 | %Define general variables 17 | time_wind = [500, 800]; 18 | n_perm = 100; 19 | 20 | %% Exact interaction 21 | 22 | if test_xls_output 23 | output_file = fullfile('outputs', 'Fclust_test.xlsx'); 24 | else 25 | output_file = false; 26 | end 27 | GND = FclustGND(GND, ... 28 | 'bins', [24, 26, 27, 29], ... 29 | 'factor_names', {'Probability', 'Emotion'}, ... 30 | 'factor_levels', [2, 2], ... 31 | 'time_wind', time_wind, ... 32 | 'n_perm', n_perm, ... 33 | 'alpha', 0.05, ... 34 | 'exclude_chans', {'centroparietal_cluster'}, ... 35 | 'save_GND', 'no', ... 36 | 'chan_hood', 75, ... 37 | 'thresh_p', .05, ... 38 | 'plot_raster', 'yes', ... 39 | 'output_file', output_file); 40 | 41 | %F==t^2 42 | [~, start_sample] = min(abs( GND.time_pts - time_wind(1) )); 43 | [~, end_sample ] = min(abs( GND.time_pts - time_wind(2) )); 44 | assert(all(all(GND.F_tests(end).F_obs.ProbabilityXEmotion - GND.grands_t(1:32, start_sample:end_sample, 51).^2 < 1e-4))); 45 | 46 | 47 | %% One-way ANOVA 48 | 49 | if test_xls_output 50 | output_file = fullfile('outputs', 'Fclust_test_oneway.xlsx'); 51 | else 52 | output_file = false; 53 | end 54 | GND = FclustGND(GND, ... 55 | 'bins', [24, 26, 27], ... 56 | 'factor_names', {'NEU_Probability'}, ... 57 | 'factor_levels', 3, ... 58 | 'time_wind', time_wind, ... 59 | 'n_perm', n_perm, ... 60 | 'alpha', 0.05, ... 61 | 'exclude_chans', {'centroparietal_cluster'}, ... 62 | 'save_GND', 'no', ... 63 | 'chan_hood', 75, ... 64 | 'thresh_p', .05, ... 65 | 'plot_raster', 'yes', ... 66 | 'output_file', output_file); 67 | 68 | 69 | %% Three-way approximate interaction 70 | 71 | GND = FclustGND(GND, ... 72 | 'bins', [1:22, 24:28], ... 73 | 'factor_names', {'Probabiltiy', 'Category', 'Study'}, ... 74 | 'factor_levels', [3, 3, 3], ... 75 | 'time_wind', time_wind, ... 76 | 'n_perm', n_perm, ... 77 | 'alpha', 0.05, ... 78 | 'exclude_chans', {'centroparietal_cluster'}, ... 79 | 'save_GND', 'no', ... 80 | 'chan_hood', 75, ... 81 | 'thresh_p', .05, ... 82 | 'plot_raster', 'no'); 83 | 84 | 85 | %% Mean window 86 | 87 | if test_xls_output 88 | output_file = fullfile('outputs', 'FclustGND_meanwind_test.xlsx'); 89 | else 90 | output_file = false; 91 | end 92 | GND = FclustGND(GND, ... 93 | 'bins', [24, 26, 27, 29], ... 94 | 'factor_names', {'Probability', 'Emotion'}, ... 95 | 'factor_levels', [2, 2], ... 96 | 'time_wind', time_wind, ... 97 | 'mean_wind', 'yes', ... 98 | 'n_perm', n_perm, ... 99 | 'alpha', 0.05, ... 100 | 'exclude_chans', {'centroparietal_cluster'}, ... 101 | 'save_GND', 'no', ... 102 | 'chan_hood', 75, ... 103 | 'thresh_p', .05, ... 104 | 'plot_raster', 'yes', ... 105 | 'output_file', output_file); 106 | 107 | %Calculate t-test on same data 108 | GND = clustGND(GND, 51, ... 109 | 'chan_hood', 75, ... 110 | 'n_perm', n_perm, ... 111 | 'time_wind', time_wind, ... 112 | 'mean_wind', 'yes', ... 113 | 'exclude_chans', {'centroparietal_cluster'}, ... 114 | 'plot_raster', 'no', ... 115 | 'plot_gui', 'no', ... 116 | 'plot_mn_topo', 'no', ... 117 | 'save_GND', 'no', ... 118 | 'verblevel', 2); 119 | 120 | %F==t^2 121 | assert(all(GND.F_tests(end).F_obs.ProbabilityXEmotion - GND.t_tests(end).data_t.^2 < 1e-9)) 122 | -------------------------------------------------------------------------------- /testing/D_FfdrGNDTest.m: -------------------------------------------------------------------------------- 1 | %Test FfdrGND function 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 12 June 2020 5 | 6 | global test_xls_output 7 | if isempty(test_xls_output) 8 | test_xls_output = true; 9 | end 10 | 11 | %Load a GND for testing 12 | %Load a GND for testing 13 | FMUT_dir = fileparts(fileparts(which('A_setupTest'))); 14 | load(fullfile(FMUT_dir, 'testing', 'data', 'EmProb_13subs_Test.GND'), '-mat'); 15 | 16 | %Define general variables 17 | time_wind = [500, 800]; 18 | [~, start_sample] = min(abs( GND.time_pts - time_wind(1) )); 19 | [~, end_sample ] = min(abs( GND.time_pts - time_wind(2) )); 20 | 21 | %% BH method 22 | 23 | if test_xls_output 24 | output_file = fullfile('outputs', 'Ffdr_test.xlsx'); 25 | else 26 | output_file = false; 27 | end 28 | GND = FfdrGND(GND, ... 29 | 'bins', [24, 26, 27, 29], ... 30 | 'factor_names', {'Probability', 'Emotion'}, ... 31 | 'factor_levels', [2, 2], ... 32 | 'method', 'bh', ... 33 | 'time_wind', time_wind, ... 34 | 'exclude_chans', {'centroparietal_cluster'}, ... 35 | 'save_GND', 'no', ... 36 | 'verblevel', 2, ... 37 | 'output_file', output_file, ... 38 | 'plot_raster', 'yes'); 39 | 40 | %F==t^2 41 | assert(all(all(GND.F_tests(end).F_obs.ProbabilityXEmotion - GND.grands_t(1:32, start_sample:end_sample, 51).^2 < 1e-4))); 42 | 43 | %Calculate t-test on the same data 44 | GND = tfdrGND(GND, 54, ... 45 | 'time_wind', time_wind, ... 46 | 'exclude_chans', {'centroparietal_cluster'}, ... 47 | 'method', 'bh', ... 48 | 'save_GND', 'no', ... 49 | 'plot_gui', 'no', ... 50 | 'plot_raster', 'no', ... 51 | 'verblevel', 2); 52 | 53 | %Critical F == critical t^2 54 | assert(GND.F_tests(end).F_crit.Emotion - GND.t_tests(end).crit_t(1)^2 < 1e-4); 55 | %P-values are the same 56 | assert(all(all(GND.F_tests(end).adj_pval.Emotion - GND.t_tests(end).adj_pval < 1e-4))) 57 | 58 | 59 | %% Oneway ANOVA 60 | 61 | if test_xls_output 62 | output_file = fullfile('outputs', 'Ffdr_test_oneway.xlsx'); 63 | else 64 | output_file = false; 65 | end 66 | GND = FfdrGND(GND, ... 67 | 'bins', [24, 26, 27], ... 68 | 'factor_names', {'NEU_Probability'}, ... 69 | 'factor_levels', 3, ... 70 | 'method', 'bh', ... 71 | 'time_wind', time_wind, ... 72 | 'exclude_chans', {'centroparietal_cluster'}, ... 73 | 'save_GND', 'no', ... 74 | 'verblevel', 2, ... 75 | 'output_file', output_file, ... 76 | 'plot_raster', 'yes', ... 77 | 'sphericity_corr', 'gg'); 78 | 79 | %% BY method 80 | 81 | GND = FfdrGND(GND, ... 82 | 'bins', [24, 26, 27, 29], ... 83 | 'factor_names', {'Probability', 'Emotion'}, ... 84 | 'factor_levels', [2, 2], ... 85 | 'method', 'by', ... 86 | 'time_wind', time_wind, ... 87 | 'exclude_chans', {'centroparietal_cluster'}, ... 88 | 'save_GND', 'no', ... 89 | 'verblevel', 2, ... 90 | 'plot_raster', 'no', ... 91 | 'sphericity_corr', 'lb'); 92 | 93 | %F==t^2 94 | assert(all(all(GND.F_tests(end).F_obs.ProbabilityXEmotion - GND.grands_t(1:32, start_sample:end_sample, 51).^2 < 1e-4))); 95 | 96 | %Calculate t-test on the same data 97 | GND = tfdrGND(GND, 54, ... 98 | 'time_wind', time_wind, ... 99 | 'exclude_chans', {'centroparietal_cluster'}, ... 100 | 'method', 'by', ... 101 | 'save_GND', 'no', ... 102 | 'plot_gui', 'no', ... 103 | 'plot_raster', 'no', ... 104 | 'verblevel', 2); 105 | 106 | %Critical F == critical t^2 107 | assert(GND.F_tests(end).F_crit.Emotion(1) - GND.t_tests(end).crit_t(1)^2 < 1e-4); 108 | %P-values are the same 109 | assert(all(all(GND.F_tests(end).adj_pval.Emotion - GND.t_tests(end).adj_pval < 1e-4))) 110 | 111 | %% BKY method 112 | 113 | GND = FfdrGND(GND, ... 114 | 'bins', [24, 26, 27, 29], ... 115 | 'factor_names', {'Probability', 'Emotion'}, ... 116 | 'factor_levels', [2, 2], ... 117 | 'method', 'bky', ... 118 | 'time_wind', time_wind, ... 119 | 'exclude_chans', {'centroparietal_cluster'}, ... 120 | 'save_GND', 'no', ... 121 | 'verblevel', 2, ... 122 | 'plot_raster', 'no', ... 123 | 'sphericity_corr', 'none'); 124 | 125 | %F==t^2 126 | assert(all(all(GND.F_tests(end).F_obs.ProbabilityXEmotion - GND.grands_t(1:32, start_sample:end_sample, 51).^2 < 1e-4))); 127 | 128 | %Calculate t-test on the same data 129 | GND = tfdrGND(GND, 54, ... 130 | 'time_wind', time_wind, ... 131 | 'exclude_chans', {'centroparietal_cluster'}, ... 132 | 'method', 'bky', ... 133 | 'save_GND', 'no', ... 134 | 'plot_gui', 'no', ... 135 | 'plot_raster', 'no', ... 136 | 'verblevel', 2); 137 | 138 | %Critical F == critical t^2 139 | assert(GND.F_tests(end).F_crit.Emotion - GND.t_tests(end).crit_t(1)^2 < 1e-4); 140 | 141 | %% Mean window 142 | 143 | if test_xls_output 144 | output_file = fullfile('outputs', 'Ffdr_test_mean_wind.xlsx'); 145 | else 146 | output_file = false; 147 | end 148 | GND = FfdrGND(GND, ... 149 | 'bins', [24, 26, 27, 29], ... 150 | 'factor_names', {'Probability', 'Emotion'}, ... 151 | 'factor_levels', [2, 2], ... 152 | 'method', 'bh', ... 153 | 'time_wind', time_wind, ... 154 | 'mean_wind', 'yes', ... 155 | 'exclude_chans', {'centroparietal_cluster'}, ... 156 | 'save_GND', 'no', ... 157 | 'verblevel', 2, ... 158 | 'output_file', output_file, ... 159 | 'plot_raster', 'yes'); 160 | 161 | 162 | 163 | %Calculate t-test on the same data 164 | GND = tfdrGND(GND, 54, ... 165 | 'time_wind', time_wind, ... 166 | 'mean_wind', 'yes', ... 167 | 'exclude_chans', {'centroparietal_cluster'}, ... 168 | 'method', 'bh', ... 169 | 'save_GND', 'no', ... 170 | 'plot_gui', 'no', ... 171 | 'plot_raster', 'no', ... 172 | 'verblevel', 2); 173 | 174 | 175 | %F==t^2 176 | assert(all(GND.F_tests(end).F_obs.Emotion - GND.t_tests(end).data_t.^2 < 1e-9)) 177 | %Critical F == critical t^2 178 | assert(GND.F_tests(end).F_crit.Emotion - GND.t_tests(end).crit_t(1)^2 < 1e-4); 179 | %P-values are the same 180 | assert(all(all(GND.F_tests(end).adj_pval.Emotion - GND.t_tests(end).adj_pval < 1e-4))) 181 | -------------------------------------------------------------------------------- /testing/E_FmaxGRPTest.m: -------------------------------------------------------------------------------- 1 | %Test FmaxGRP function 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 12 June 2020 5 | 6 | global test_xls_output 7 | if isempty(test_xls_output) 8 | test_xls_output = true; 9 | end 10 | 11 | %Load GRP 12 | FMUT_dir = fileparts(fileparts(which('A_setupTest'))); 13 | load(fullfile(FMUT_dir, 'testing', 'data', 'Disflu_GroupLevel.GRP'), '-mat'); 14 | 15 | %Define some general variables 16 | time_wind = [300, 500]; 17 | include_chans = {'Fz', 'Cz', 'Pz'}; 18 | [~, start_sample] = min(abs( GRP.time_pts - time_wind(1) )); 19 | [~, end_sample ] = min(abs( GRP.time_pts - time_wind(2) )); 20 | electrodes = NaN(1, length(include_chans)); 21 | for c = 1:length(include_chans) 22 | electrodes(c) = find(strcmp(include_chans(c), {GRP.chanlocs.labels})); 23 | end 24 | n_perm = 100; 25 | 26 | 27 | %% Exact interaction 28 | 29 | if test_xls_output 30 | output_file = fullfile('outputs', 'FmaxGRP_test.xlsx'); 31 | else 32 | output_file = false; 33 | end 34 | GRP = FmaxGRP(GRP, ... 35 | 'bins', 4:7, ... 36 | 'bg_factor_name', 'reliability', ... 37 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 38 | 'wg_factor_levels', [2, 2], ... 39 | 'time_wind', time_wind, ... 40 | 'include_chans', include_chans, ... 41 | 'n_perm', n_perm, ... 42 | 'alpha', 0.05, ... 43 | 'output_file', output_file, ... 44 | 'save_GRP', 'no'); 45 | 46 | %F==t^2 47 | assert(all(all(GRP.F_tests(end).F_obs.expectednessXdisfluencyXreliability - GRP.grands_t(electrodes, start_sample:end_sample, 78).^2 < 1e-4))); 48 | 49 | 50 | %% One-way 51 | %Completely randomized ANOVA 52 | 53 | if test_xls_output 54 | output_file = fullfile('outputs', 'FmaxGRP_oneway_test.xlsx'); 55 | else 56 | output_file = false; 57 | end 58 | GRP = FmaxGRP(GRP, ... 59 | 'bins', 4, ... 60 | 'bg_factor_name', 'reliability', ... 61 | 'time_wind', time_wind, ... 62 | 'include_chans', include_chans, ... 63 | 'n_perm', 1e4, ... 64 | 'alpha', 0.05, ... 65 | 'output_file', output_file, ... 66 | 'save_GRP', 'no'); 67 | 68 | %F==t^2 69 | assert(all(all(GRP.F_tests(end).F_obs - GRP.grands_t(electrodes, start_sample:end_sample, 4).^2 < 1e-4))); 70 | 71 | GRP = tmaxGRP(GRP, 4, ... 72 | 'time_wind', time_wind, ... 73 | 'include_chans', include_chans, ... 74 | 'n_perm', 1e4, ... 75 | 'alpha', 0.05, ... 76 | 'save_GRP', 'no', ... 77 | 'plot_gui', 'no', ... 78 | 'plot_raster', 'no', ... 79 | 'verblevel', 2); 80 | 81 | %Critical F == critical t^2 82 | assert(GRP.F_tests(end).F_crit - GRP.t_tests(end).crit_t(1)^2 < 0.5); 83 | 84 | 85 | %% Aproximate interaction 86 | 87 | GRP = FmaxGRP(GRP, ... 88 | 'bins', [1, 4:9, 22:23], ... 89 | 'bg_factor_name', 'reliability', ... 90 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 91 | 'wg_factor_levels', [3, 3], ... 92 | 'time_wind', time_wind, ... 93 | 'include_chans', include_chans, ... 94 | 'n_perm', n_perm, ... 95 | 'alpha', 0.05, ... 96 | 'plot_raster', 'no', ... 97 | 'save_GRP', 'no'); 98 | 99 | %% Mean window 100 | 101 | if test_xls_output 102 | output_file = fullfile('outputs', 'FmaxGRP_meanwindow_test.xlsx'); 103 | else 104 | output_file = false; 105 | end 106 | GRP = FmaxGRP(GRP, ... 107 | 'bins', 4:7, ... 108 | 'bg_factor_name', 'reliability', ... 109 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 110 | 'wg_factor_levels', [2, 2], ... 111 | 'time_wind', time_wind, ... 112 | 'include_chans', include_chans, ... 113 | 'n_perm', n_perm, ... 114 | 'mean_wind', 'yes', ... 115 | 'output_file', output_file, ... 116 | 'save_GRP', 'no'); 117 | -------------------------------------------------------------------------------- /testing/F_FclustGRPTest.m: -------------------------------------------------------------------------------- 1 | %Test FclustGRP function 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 12 June 2020 5 | 6 | global test_xls_output; 7 | if isempty(test_xls_output) 8 | test_xls_output = true; 9 | end 10 | 11 | %Load GRP 12 | FMUT_dir = fileparts(fileparts(which('A_setupTest'))); 13 | load(fullfile(FMUT_dir, 'testing', 'data', 'Disflu_GroupLevel.GRP'), '-mat'); 14 | 15 | %Define general variables 16 | time_wind = [300, 500]; 17 | n_perm = 100; 18 | chan_hood = .9; 19 | [~, start_sample] = min(abs( GRP.time_pts - time_wind(1) )); 20 | [~, end_sample ] = min(abs( GRP.time_pts - time_wind(2) )); 21 | 22 | %% Exact interaction 23 | 24 | if test_xls_output 25 | output_file = fullfile('outputs', 'FclustGRP_test.xlsx'); 26 | else 27 | output_file = false; 28 | end 29 | GRP = FclustGRP(GRP, ... 30 | 'bins', 4:7, ... 31 | 'bg_factor_name', 'reliability', ... 32 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 33 | 'wg_factor_levels', [2, 2], ... 34 | 'time_wind', time_wind, ... 35 | 'n_perm', n_perm, ... 36 | 'alpha', 0.05, ... 37 | 'output_file', output_file, ... 38 | 'chan_hood', chan_hood, ... 39 | 'save_GRP', 'no'); 40 | 41 | %F==t^2 42 | assert(all(all(GRP.F_tests(end).F_obs.expectednessXdisfluencyXreliability - GRP.grands_t(:, start_sample:end_sample, 78).^2 < 1e-4))); 43 | 44 | 45 | %% One-way 46 | %Completely randomized 47 | 48 | if test_xls_output 49 | output_file = fullfile('outputs', 'FclustGRP_oneway_test.xlsx'); 50 | else 51 | output_file = false; 52 | end 53 | GRP = FclustGRP(GRP, ... 54 | 'bins', 4, ... 55 | 'bg_factor_name', 'reliability', ... 56 | 'time_wind', time_wind, ... 57 | 'n_perm', n_perm, ... 58 | 'alpha', 0.05, ... 59 | 'output_file', output_file, ... 60 | 'chan_hood', chan_hood, ... 61 | 'save_GRP', 'no'); 62 | 63 | %F==t^2 64 | assert(all(all(GRP.F_tests(end).F_obs - GRP.grands_t(:, start_sample:end_sample, 4).^2 < 1e-4))); 65 | 66 | %% Aproximate interaction 67 | 68 | GRP = FclustGRP(GRP, ... 69 | 'bins', [1, 4:9, 22:23], ... 70 | 'bg_factor_name', 'reliability', ... 71 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 72 | 'wg_factor_levels', [3, 3], ... 73 | 'time_wind', time_wind, ... 74 | 'n_perm', n_perm, ... 75 | 'chan_hood', chan_hood, ... 76 | 'plot_raster', 'no', ... 77 | 'save_GRP', 'no'); 78 | 79 | 80 | %% Mean window 81 | 82 | if test_xls_output 83 | output_file = fullfile('outputs', 'FclustGRP_meanwindow_test.xlsx'); 84 | else 85 | output_file = false; 86 | end 87 | GRP = FclustGRP(GRP, ... 88 | 'bins', 4:7, ... 89 | 'bg_factor_name', 'reliability', ... 90 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 91 | 'wg_factor_levels', [2, 2], ... 92 | 'time_wind', time_wind, ... 93 | 'mean_wind', 'yes', ... 94 | 'n_perm', n_perm, ... 95 | 'output_file', output_file, ... 96 | 'chan_hood', chan_hood, ... 97 | 'save_GRP', 'no'); 98 | 99 | -------------------------------------------------------------------------------- /testing/G_FfdrGRPTest.m: -------------------------------------------------------------------------------- 1 | %Test FfdrGRP function 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 12 June 2020 5 | 6 | global test_xls_output 7 | if isempty(test_xls_output) 8 | test_xls_output = true; 9 | end 10 | 11 | %Load GRP 12 | FMUT_dir = fileparts(fileparts(which('A_setupTest'))); 13 | load(fullfile(FMUT_dir, 'testing', 'data', 'Disflu_GroupLevel.GRP'), '-mat'); 14 | 15 | %Define some general variables 16 | time_wind = [300, 500]; 17 | [~, start_sample] = min(abs( GRP.time_pts - time_wind(1) )); 18 | [~, end_sample ] = min(abs( GRP.time_pts - time_wind(2) )); 19 | 20 | 21 | %% BH 22 | 23 | if test_xls_output 24 | output_file = fullfile('outputs', 'FfdrGRP_test.xlsx'); 25 | else 26 | output_file = false; 27 | end 28 | GRP = FfdrGRP(GRP, ... 29 | 'bins', 4:7, ... 30 | 'bg_factor_name', 'reliability', ... 31 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 32 | 'wg_factor_levels', [2, 2], ... 33 | 'method', 'bh', ... 34 | 'time_wind', time_wind, ... 35 | 'output_file', output_file, ... 36 | 'save_GRP', 'no'); 37 | 38 | %F==t^2 39 | assert(all(all(GRP.F_tests(end).F_obs.expectednessXdisfluencyXreliability - GRP.grands_t(:, start_sample:end_sample, 78).^2 < 1e-4))); 40 | 41 | 42 | %% One-way 43 | %Completely randomized ANOVA 44 | 45 | if test_xls_output 46 | output_file = fullfile('outputs', 'FfdrGRP_oneway_test.xlsx'); 47 | else 48 | output_file = false; 49 | end 50 | GRP = FfdrGRP(GRP, ... 51 | 'bins', 4, ... 52 | 'bg_factor_name', 'reliability', ... 53 | 'time_wind', time_wind, ... 54 | 'method', 'bh', ... 55 | 'output_file', output_file, ... 56 | 'plot_raster', 'no', ... 57 | 'save_GRP', 'no'); 58 | 59 | %F==t^2 60 | assert(all(all(GRP.F_tests(end).F_obs - GRP.grands_t(:, start_sample:end_sample, 4).^2 < 1e-4))); 61 | 62 | GRP = tfdrGRP(GRP, 4, ... 63 | 'time_wind', time_wind, ... 64 | 'save_GRP', 'no', ... 65 | 'plot_gui', 'no', ... 66 | 'plot_raster', 'no', ... 67 | 'verblevel', 2); 68 | 69 | %p is the same 70 | assert(all(GRP.F_tests(end).adj_pval(:) - GRP.t_tests(end).adj_pval(:) < 0.05)); 71 | 72 | 73 | %% Mean window 74 | 75 | if test_xls_output 76 | output_file = fullfile('outputs', 'FfdrGRP_meanwindow_test.xlsx'); 77 | else 78 | output_file = false; 79 | end 80 | GRP = FfdrGRP(GRP, ... 81 | 'bins', 4:7, ... 82 | 'bg_factor_name', 'reliability', ... 83 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 84 | 'wg_factor_levels', [2, 2], ... 85 | 'time_wind', time_wind, ... 86 | 'mean_wind', 'yes', ... 87 | 'output_file', output_file, ... 88 | 'save_GRP', 'no'); 89 | -------------------------------------------------------------------------------- /testing/H_VariousTest.m: -------------------------------------------------------------------------------- 1 | %Test Various things 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 12 June 2020 5 | 6 | %Load a GND for testing 7 | FMUT_dir = fileparts(fileparts(which('A_setupTest'))); 8 | load(fullfile(FMUT_dir, 'testing', 'data', 'EmProb_13subs_Test.GND'), '-mat'); 9 | load(fullfile(FMUT_dir, 'testing', 'data', 'Disflu_GroupLevel.GRP'), '-mat'); 10 | 11 | 12 | %% Check F-values calculated by approximate int code RB 13 | 14 | %%% Two-way %%% 15 | 16 | data = normrnd(0, 1, [32, 167, 2, 2, 16]); 17 | dims = [3, 4]; 18 | n_perm = 10; 19 | 20 | %F-test 21 | F_obs = perm_rbANOVA(data, dims, n_perm, false); 22 | 23 | %t-test 24 | data = squeeze(data(:, :, 1, :, :) - data(:, :, 2, :, :)); 25 | data = squeeze(data(:, :, 1, :) - data(:, :, 2, :)); 26 | mn = mean(data, 3); 27 | stderr = std(data, [], 3) / sqrt(size(data, 3)); 28 | t_vals = mn ./ stderr; 29 | 30 | assert(all(F_obs(:) - t_vals(:).^2 < 1e-9)) 31 | 32 | %%% Three-way %%% 33 | 34 | data = normrnd(0, 1, [32, 167, 2, 2, 2, 16]); 35 | dims = [3, 4, 5]; 36 | n_perm = 10; 37 | 38 | %F-test 39 | F_obs = perm_rbANOVA(data, dims, n_perm, false); 40 | 41 | %t-test 42 | data = squeeze(data(:, :, 1, :, :, :) - data(:, :, 2, :, :, :)); 43 | data = squeeze(data(:, :, 1, :, :) - data(:, :, 2, :, :)); 44 | data = squeeze(data(:, :, 1, :) - data(:, :, 2, :)); 45 | mn = mean(data, 3); 46 | stderr = std(data, [], 3) / sqrt(size(data, 3)); 47 | t_vals = mn ./ stderr; 48 | 49 | assert(all(F_obs(:) - t_vals(:).^2 < 1e-9)) 50 | 51 | 52 | %% Check F-values calculated by approximate int code SP 53 | 54 | %%% Two-way %%% 55 | 56 | data = normrnd(0, 1, [32, 167, 2, 16]); 57 | cond_subs = [8, 8]; 58 | dims = [3, 4]; 59 | n_perm = 10; 60 | 61 | %F-test 62 | F_obs = perm_spANOVA(data, cond_subs, dims, n_perm, false); 63 | 64 | %t-test 65 | data = reshape(data(:, :, 1, :) - data(:, :, 2, :), [32, 167, 16]); 66 | data = permute(data, [3, 1, 2]); 67 | [~, ~, ~, stats] = ttest2(data(1:8, :, :), data(9:16, :, :)); 68 | 69 | assert(all(F_obs(:) - stats.tstat(:).^2 < 1e-9)) 70 | 71 | %%% Three-way %%% 72 | 73 | data = normrnd(0, 1, [32, 167, 2, 2, 16]); 74 | cond_subs = [8, 8]; 75 | dims = [3, 4, 5]; 76 | n_perm = 10; 77 | 78 | %F-test 79 | F_obs = perm_spANOVA(data, cond_subs, dims, n_perm, false); 80 | 81 | %t-test 82 | data = reshape(data(:, :, 1, :, :) - data(:, :, 2, :, :), [32, 167, 2, 16]); 83 | data = reshape(data(:, :, 1, :) - data(:, :, 2, :), [32, 167, 16]); 84 | data = permute(data, [3, 1, 2]); 85 | [~, ~, ~, stats] = ttest2(data(1:8, :, :), data(9:16, :, :)); 86 | 87 | assert(all(F_obs(:) - stats.tstat(:).^2 < 1e-9)) 88 | 89 | 90 | %% Output of functions is compatible and test get_mean_amplitude 91 | 92 | time_wind = [300, 500]; 93 | n_perm = 100; 94 | chan_hood = .9; 95 | 96 | GND = FclustGND(GND, ... 97 | 'bins', [24, 26, 27, 29], ... 98 | 'factor_names', {'Probability', 'Emotion'}, ... 99 | 'factor_levels', [2, 2], ... 100 | 'time_wind', time_wind, ... 101 | 'n_perm', n_perm, ... 102 | 'chan_hood', chan_hood, ... 103 | 'save_GND', 'no', ... 104 | 'output_file', false, ... 105 | 'plot_raster', 'no'); 106 | 107 | GND = FfdrGND(GND, ... 108 | 'bins', [24, 26, 27, 29], ... 109 | 'factor_names', {'Probability', 'Emotion'}, ... 110 | 'factor_levels', [2, 2], ... 111 | 'time_wind', time_wind, ... 112 | 'output_file', false, ... 113 | 'save_GND', 'no', ... 114 | 'plot_raster', 'no'); 115 | 116 | GND = FmaxGND(GND, ... 117 | 'bins', [24, 26, 27, 29], ... 118 | 'factor_names', {'Probability', 'Emotion'}, ... 119 | 'factor_levels', [2, 2], ... 120 | 'time_wind', time_wind, ... 121 | 'n_perm', n_perm, ... 122 | 'save_GND', 'no', ... 123 | 'plot_raster', 'no'); 124 | 125 | GRP = FmaxGRP(GRP, ... 126 | 'bins', 4:7, ... 127 | 'bg_factor_name', 'reliability', ... 128 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 129 | 'wg_factor_levels', [2, 2], ... 130 | 'time_wind', time_wind, ... 131 | 'n_perm', n_perm, ... 132 | 'save_GRP', 'no', ... 133 | 'plot_raster', 'no'); 134 | 135 | GRP = FclustGRP(GRP, ... 136 | 'bins', 4:7, ... 137 | 'bg_factor_name', 'reliability', ... 138 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 139 | 'wg_factor_levels', [2, 2], ... 140 | 'time_wind', time_wind, ... 141 | 'n_perm', n_perm, ... 142 | 'alpha', 0.05, ... 143 | 'output_file', false, ... 144 | 'chan_hood', chan_hood, ... 145 | 'save_GRP', 'no', ... 146 | 'plot_raster', 'no'); 147 | 148 | GRP = FfdrGRP(GRP, ... 149 | 'bins', 4:7, ... 150 | 'bg_factor_name', 'reliability', ... 151 | 'wg_factor_names', {'expectedness', 'disfluency'}, ... 152 | 'wg_factor_levels', [2, 2], ... 153 | 'method', 'bh', ... 154 | 'time_wind', time_wind, ... 155 | 'output_file', false, ... 156 | 'save_GRP', 'no', ... 157 | 'plot_raster', 'no'); 158 | 159 | 160 | % Test get_mean_amplitude function 161 | 162 | %With GND 163 | for i = 1:length(GND.F_tests) 164 | mean_data_GND = get_mean_amplitude(GND, i, 'effect', 'Emotion', 'output_file', sprintf('outputs/test_get_mean_amplitude_GND_%s.csv', GND.F_tests(i).mult_comp_method)); 165 | end 166 | 167 | %With GRP 168 | for i = 1:length(GRP.F_tests) 169 | mean_data_GRP = get_mean_amplitude(GRP, i, 'effect', 'expectedness', 'output_file', sprintf('outputs/test_get_mean_amplitude_GRP_%s.csv', GRP.F_tests(i).mult_comp_method)); 170 | end 171 | -------------------------------------------------------------------------------- /testing/I_ANOVATest.m: -------------------------------------------------------------------------------- 1 | %Test FMUT ANOVA calculations against MATLAB ANOVA calculations 2 | % 3 | %Author: Eric Fields 4 | %Version Date: 12 June 2020 5 | 6 | %Simulation parameters 7 | n_electrodes = 32; 8 | n_time_pts = 40; 9 | n_subs = 16; 10 | 11 | %% RB & SP DESIGNS PARAMETRIC ANOVA 12 | 13 | %Designs to test 14 | anova_designs = {2, ... 15 | 5, ... 16 | [2, 2], ... 17 | [4, 2], ... 18 | [2, 5], ... 19 | [3, 4], ... 20 | [2, 2, 2], ... 21 | [3, 2, 2], ... 22 | [2, 3, 2], ... 23 | [2, 2, 3], ... 24 | [3, 3, 2], ... 25 | [2, 3, 3], ... 26 | [4, 2, 5], ... 27 | [3, 4, 3], ... 28 | [2, 2, 2, 2], ... 29 | [4, 3, 5, 2], ... 30 | [2, 2, 3, 2], ... 31 | [3, 4, 2, 3]}; 32 | 33 | sphericity_corrections = {'none', 'GG', 'HF', 'LB'}; 34 | 35 | for bg_factor = [false, true] 36 | 37 | if bg_factor 38 | cond_subs = [8,8]; 39 | else 40 | cond_subs = []; 41 | end 42 | 43 | for m = 1:length(anova_designs) 44 | 45 | wg_design = anova_designs{m}; 46 | 47 | if sum(wg_design>2) > 2 48 | continue; 49 | end 50 | 51 | %Choose random sphericity correction 52 | if sum(wg_design>2) < 2 && isempty(cond_subs) 53 | sphericity_corr = sphericity_corrections{randi(numel(sphericity_corrections))}; 54 | else 55 | sphericity_corr = 'none'; 56 | end 57 | 58 | %Simulate data 59 | data = randn([n_electrodes, n_time_pts, wg_design, n_subs]); 60 | 61 | %Choose random electrode and time point for testing 62 | e = 1; %randi([1,n_electrodes]); 63 | t = 1; %randi([1,n_time_pts]); 64 | 65 | %Generic variable names 66 | var_names = cell(1, length(wg_design)); 67 | for i = 1:length(wg_design) 68 | var_names{i} = char(64+i); 69 | end 70 | [effects, effects_labels] = get_effects(var_names); 71 | 72 | %MATLAB ANOVA 73 | oneway_data = reshape(data, n_electrodes, n_time_pts, [], n_subs); 74 | rm_data = squeeze(oneway_data(e,t,:,:))'; 75 | [rm, ranovatbl] = matlab_ANOVA(rm_data, wg_design, cond_subs, var_names); 76 | %disp(ranovatbl); 77 | 78 | %Calculate all effects in model and compare to MATLAB ANOVA 79 | for i = 1:length(effects) 80 | 81 | %%% Within subjects effects %%% 82 | 83 | %FMUT calculations 84 | dims = effects{i}+2; 85 | test_results = calc_param_ANOVA(data, cond_subs, dims, 0.05, 'none', sphericity_corr); 86 | 87 | %Check results for a random electrode and time point 88 | rm_table_row = ['(Intercept):' strrep(effects_labels{i}, 'X', ':')]; 89 | if strcmp(sphericity_corr, 'none') 90 | suff = ''; 91 | else 92 | suff = sphericity_corr; 93 | end 94 | assert(abs(test_results.p(e,t) - ranovatbl{rm_table_row, ['pValue' suff]}) < 1e-9); 95 | 96 | %%% Group interactions %%% 97 | 98 | if ~isempty(cond_subs) 99 | 100 | %FMUT calculations 101 | dims = [effects{i}+2 ndims(data)]; 102 | test_results = calc_param_ANOVA(data, cond_subs, dims, 0.05, 'none', sphericity_corr); 103 | 104 | %Check results for a random electrode and time point 105 | rm_table_row = ['group:' strrep(effects_labels{i}, 'X', ':')]; 106 | if strcmp(sphericity_corr, 'none') 107 | suff = ''; 108 | else 109 | suff = sphericity_corr; 110 | end 111 | assert(abs(test_results.p(e,t) - ranovatbl{rm_table_row, ['pValue' suff]}) < 1e-9); 112 | 113 | end 114 | 115 | end 116 | 117 | %%% Group main effect %%% 118 | 119 | if ~isempty(cond_subs) 120 | test_results = calc_param_ANOVA(data, cond_subs, ndims(data), 0.05, 'none', sphericity_corr); 121 | assert(abs(test_results.p(e,t) - ranovatbl{'group', ['pValue' suff]}) < 1e-9); 122 | end 123 | 124 | end 125 | 126 | end 127 | 128 | %% CR ANOVA 129 | 130 | %Sample sizes 131 | cond_subs = [8, 8, 8]; 132 | 133 | %Generate data 134 | data = randn([n_electrodes, n_time_pts, sum(cond_subs)]); 135 | 136 | %Choose random electrode and time point for testing 137 | e = 1; %randi([1,n_electrodes]); 138 | t = 1; %randi([1,n_time_pts]); 139 | 140 | %Create group input 141 | group = {}; 142 | for g = 1:length(cond_subs) 143 | group = [group; repmat({sprintf('G%d', g)}, cond_subs(g), 1)]; %#ok 144 | end 145 | 146 | %MATLAB ANOVA 147 | cr_data = squeeze(data(e, t, :)); 148 | [~, cranovatbl] = anovan(cr_data, {group}, 'display', 'off'); 149 | 150 | %FMUT ANOVA 151 | test_results = calc_param_ANOVA(data, cond_subs, 3, 0.05, 'none', 'none'); 152 | 153 | %Check that they are equal 154 | assert(cranovatbl{2, 7} - test_results.p(e,t) < 1e-9); 155 | 156 | 157 | %% ANOVA function 158 | 159 | function [rm, ranovatbl] = matlab_ANOVA(rm_data, wg_design, cond_subs, var_names) 160 | %Calculate ANOVA with MATLAB's stats module 161 | % 162 | %INPUTS 163 | % rm_data - subjects x variables array 164 | % wg_design - number of levels of each factor from slowest to fastest 165 | % moving in rm_data columns 166 | % 167 | %OUTPUTS 168 | % rm - RepeatedMeasuresModel object 169 | % ranovatbl - Results of repeated measures anova, returned as a table 170 | 171 | n_subs = size(rm_data, 1); 172 | 173 | %Within subject design table 174 | withindesign = cell2table(cell(prod(wg_design), length(wg_design)), 'VariableNames', var_names); 175 | row = 0; 176 | if length(wg_design) ==1 177 | for a = 1:wg_design(1) 178 | row = row + 1; 179 | withindesign{row, 'A'} = {sprintf('A%d', a)}; 180 | end 181 | elseif length(wg_design) == 2 182 | for b = 1:wg_design(2) 183 | for a = 1:wg_design(1) 184 | row = row + 1; 185 | withindesign{row, 'A'} = {sprintf('A%d', a)}; 186 | withindesign{row, 'B'} = {sprintf('B%d', b)}; 187 | end 188 | end 189 | elseif length(wg_design) == 3 190 | for c = 1:wg_design(3) 191 | for b = 1:wg_design(2) 192 | for a = 1:wg_design(1) 193 | row = row + 1; 194 | withindesign{row, 'A'} = {sprintf('A%d', a)}; 195 | withindesign{row, 'B'} = {sprintf('B%d', b)}; 196 | withindesign{row, 'C'} = {sprintf('C%d', c)}; 197 | end 198 | end 199 | end 200 | elseif length(wg_design) == 4 201 | for d = 1:wg_design(4) 202 | for c = 1:wg_design(3) 203 | for b = 1:wg_design(2) 204 | for a = 1:wg_design(1) 205 | row = row + 1; 206 | withindesign{row, 'A'} = {sprintf('A%d', a)}; 207 | withindesign{row, 'B'} = {sprintf('B%d', b)}; 208 | withindesign{row, 'C'} = {sprintf('C%d', c)}; 209 | withindesign{row, 'D'} = {sprintf('D%d', d)}; 210 | end 211 | end 212 | end 213 | end 214 | end 215 | 216 | %Column names 217 | col_names = cell(1, prod(wg_design)+1); 218 | col_names{1} = 'sub'; 219 | for row = 1:size(withindesign, 1) 220 | col_names{row+1} = strjoin(withindesign{row, :}, ''); 221 | end 222 | 223 | %Create ANOVA table 224 | T = [cell2table(cellfun(@(x) sprintf('S%d', x), num2cell(1:n_subs)', 'UniformOutput', false), 'VariableNames', {'sub'}) ... 225 | array2table(rm_data)]; 226 | T.Properties.VariableNames = col_names; 227 | group = {}; 228 | 229 | %MATLAB repeated measure ANOVA 230 | if isempty(cond_subs) 231 | model_formula = sprintf('%s-%s~1', T.Properties.VariableNames{2}, T.Properties.VariableNames{end}); 232 | else 233 | for g = 1:length(cond_subs) 234 | group = [group; repmat({sprintf('G%d', g)}, cond_subs(g), 1)]; %#ok 235 | end 236 | T(:, 'group') = group; 237 | model_formula = sprintf('%s-%s~1+group', T.Properties.VariableNames{2}, T.Properties.VariableNames{end-1}); 238 | end 239 | rm = fitrm(T, model_formula, 'WithinDesign', withindesign); 240 | [~, effects_labels] = get_effects(var_names); 241 | reg_design = strrep(strjoin(effects_labels, '+'), 'X', '*'); 242 | ranovatbl = ranova(rm, 'WithinModel', reg_design); 243 | 244 | writetable(T, 'outputs/sp_test.csv'); 245 | 246 | end 247 | -------------------------------------------------------------------------------- /testing/external_check.m: -------------------------------------------------------------------------------- 1 | %For testing ANOVA output from various designs (with various reduction 2 | %schemes) against stats software (e.g., jamovi, JASP, SPSS). Each section 3 | %runs a test and outputs the data in a relevant csv for independent testing 4 | % 5 | %AUTHOR: Eric Fields 6 | %VERSION DATE: 12 June 2020 7 | 8 | %% Set-up 9 | 10 | clearvars; 11 | global VERBLEVEL; 12 | VERBLEVEL = 0; 13 | 14 | data = normrnd(0, 1, [1, 1, 3, 3, 2, 16]); 15 | n_perm = 10; 16 | alpha = .05; 17 | 18 | %% AxB interaction 19 | 20 | %FMUT 21 | dims = [3, 4]; 22 | results = calc_Fmax(data, [], dims, n_perm, alpha); 23 | fprintf('\nF_obs = %f\n', results.F_obs) 24 | fprintf('df = [%d, %d]\n\n', results.df(1), results.df(2)) 25 | 26 | %Output 27 | output_data = reshape(mean(data, 5), [9, 16])'; 28 | header = {}; 29 | for i = 1:9 30 | header = [header char(64+i)]; %#ok 31 | end 32 | T = cell2table(num2cell(output_data), 'VariableNames', header); 33 | writetable(T, 'outputs/test_3x3_int.csv') 34 | 35 | %% BxC interaction 36 | 37 | %FMUT 38 | dims = [4, 5]; 39 | results = calc_Fmax(data, [], dims, n_perm, alpha); 40 | fprintf('\nF_obs = %f\n', results.F_obs) 41 | fprintf('df = [%d, %d]\n\n', results.df(1), results.df(2)) 42 | 43 | %Output 44 | output_data = reshape(mean(data, 3), [6, 16])'; 45 | header = {}; 46 | for i = 1:6 47 | header = [header char(64+i)]; %#ok 48 | end 49 | T = cell2table(num2cell(output_data), 'VariableNames', header); 50 | writetable(T, 'outputs/test_3x2_int.csv') 51 | 52 | %% AxBxC interaction 53 | 54 | %FMUT 55 | dims = [3, 4, 5]; 56 | results = calc_Fmax(data, [], dims, n_perm, alpha); 57 | fprintf('\nF_obs = %f\n', results.F_obs) 58 | fprintf('df = [%d, %d]\n\n', results.df(1), results.df(2)) 59 | 60 | %Output 61 | output_data = reshape(data, [18, 16])'; 62 | header = {}; 63 | for i = 1:18 64 | header = [header char(64+i)]; %#ok 65 | end 66 | T = cell2table(num2cell(output_data), 'VariableNames', header); 67 | writetable(T, 'outputs/test_3x3x2_int.csv') 68 | 69 | -------------------------------------------------------------------------------- /testing/simulated_data_rm.m: -------------------------------------------------------------------------------- 1 | %Run simulated normal data to check Type I error rate and power 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 13 July 2017 5 | 6 | clearvars; 7 | 8 | %% Set-up 9 | 10 | global VERBLEVEL 11 | VERBLEVEL = 0; 12 | 13 | %Design 14 | n_electrodes = 1; 15 | n_time_pts = 1; 16 | n_subs = 16; 17 | wg_design = [2 2 2]; 18 | 19 | %Effect 20 | dims = [3 4 5]; 21 | 22 | %Parameters 23 | n_exp = 1e3; 24 | n_perm = 1e3; 25 | alpha = 0.05; 26 | 27 | %Add effects 28 | main_effect = 0; 29 | int_effect = 0; 30 | 31 | %Pre-allocate results struct 32 | test_results = repmat(struct('h', NaN(n_electrodes, n_time_pts), ... 33 | 'p', NaN(n_electrodes, n_time_pts), ... 34 | 'F_obs', NaN(n_electrodes, n_time_pts), ... 35 | 'Fmax_crit', NaN, ... 36 | 'df', [NaN, NaN], ... 37 | 'estimated_alpha', NaN, ... 38 | 'exact_test', NaN), ... 39 | n_exp, 1); 40 | 41 | 42 | %% Simulate experiments 43 | 44 | tic 45 | parfor i = 1:n_exp 46 | 47 | %Simulate null data 48 | data = normrnd(0, 1, [n_electrodes, n_time_pts, wg_design, n_subs]); 49 | 50 | %Add effects 51 | if main_effect 52 | data(:, :, 1, :) = data(:, :, 1, :) + main_effect; 53 | end 54 | if int_effect 55 | if ndims(data) == 5 56 | data(:, :, 1, 1, :) = data(:, :, 1, 1, :) + int_effect; 57 | data(:, :, 1, 2, :) = data(:, :, 1, 2, :) - int_effect; 58 | data(:, :, 2, 1, :) = data(:, :, 2, 1, :) - int_effect; 59 | data(:, :, 2, 2, :) = data(:, :, 2, 2, :) + int_effect; 60 | elseif ndims(data) == 6 61 | data(:, :, 1, 1, 1, :) = data(:, :, 1, 1, 1, :) + int_effect; 62 | data(:, :, 1, 2, 1, :) = data(:, :, 1, 2, 1, :) - int_effect; 63 | data(:, :, 2, 1, 1, :) = data(:, :, 2, 1, 1, :) - int_effect; 64 | data(:, :, 2, 2, 1, :) = data(:, :, 2, 2, 1, :) + int_effect; 65 | end 66 | end 67 | 68 | %Calculate ANOVA 69 | test_results(i) = calc_Fmax(data, [], dims, n_perm, alpha); 70 | 71 | end 72 | toc 73 | 74 | 75 | %% Report results 76 | 77 | fprintf('\nRejection rate = %.3f\n', mean(mean([test_results(:).h]))) 78 | fprintf('Mean p = %.3f\n', mean(mean([test_results(:).p]))) 79 | fprintf('Mean F_obs = %.2f\n', mean(mean([test_results(:).F_obs]))) 80 | fprintf('Mean F_crit = %.2f\n', mean(mean([test_results(:).Fmax_crit]))) 81 | fprintf('Parametric F_crit = %.2f\n\n', finv(.95, test_results(1).df(1), test_results(1).df(2))) 82 | -------------------------------------------------------------------------------- /testing/simulated_data_sp.m: -------------------------------------------------------------------------------- 1 | %Run simulated normal data to check Type I error rate and power 2 | % 3 | %AUTHOR: Eric Fields 4 | %VERSION DATE: 24 July 2017 5 | 6 | clearvars; 7 | 8 | %% Set-up 9 | 10 | global VERBLEVEL 11 | VERBLEVEL = 0; 12 | 13 | %Design 14 | n_electrodes = 1; 15 | n_time_pts = 1; 16 | wg_design = [3 3]; 17 | cond_subs = [8 8]; 18 | n_subs = sum(cond_subs); 19 | 20 | %Effect 21 | dims = [3, 4, 5]; 22 | 23 | %Parameters 24 | n_exp = 1e3; 25 | n_perm = 1e3; 26 | alpha = 0.05; 27 | 28 | %Add effects 29 | wg_effect = 0; 30 | bg_effect = 0; 31 | int_effect = 0; 32 | 33 | %Pre-allocate results struct 34 | test_results = repmat(struct('h', NaN(n_electrodes, n_time_pts), ... 35 | 'p', NaN(n_electrodes, n_time_pts), ... 36 | 'F_obs', NaN(n_electrodes, n_time_pts), ... 37 | 'Fmax_crit', NaN, ... 38 | 'df', [NaN, NaN], ... 39 | 'estimated_alpha', NaN, ... 40 | 'exact_test', NaN), ... 41 | n_exp, 1); 42 | 43 | 44 | %% Simulate experiments 45 | 46 | tic 47 | for i = 1:n_exp 48 | 49 | %Simulate null data 50 | data = normrnd(0, 1, [n_electrodes, n_time_pts, wg_design, n_subs]); 51 | 52 | %Add effects 53 | if wg_effect 54 | data(:, :, 1, :) = data(:, :, 1, :) + wg_effect; 55 | end 56 | if bg_effect 57 | data(1:numel(data)/size(data,ndims(data))*cond_subs(1)) = data(1:numel(data)/size(data,ndims(data))*cond_subs(1)) + bg_effect; 58 | end 59 | if int_effect 60 | Asubs = 1:cond_subs(1); 61 | Bsubs = cond_subs(1)+1:cond_subs(1)+cond_subs(2); 62 | if ndims(data) == 4 && isequal(dims, [3, 4]) 63 | data(:, :, 1, Asubs) = data(:, :, 1, Asubs) + int_effect; 64 | data(:, :, 1, Bsubs) = data(:, :, 1, Bsubs) - int_effect; 65 | data(:, :, 2, Asubs) = data(:, :, 2, Asubs) - int_effect; 66 | data(:, :, 2, Bsubs) = data(:, :, 2, Bsubs) + int_effect; 67 | elseif ndims(data) == 5 68 | data(:, :, 1, 1, Asubs) = data(:, :, 1, 1, Asubs) + int_effect; 69 | data(:, :, 1, 2, Asubs) = data(:, :, 1, 2, Asubs) - int_effect; 70 | data(:, :, 2, 1, Asubs) = data(:, :, 2, 1, Asubs) - int_effect; 71 | data(:, :, 2, 2, Asubs) = data(:, :, 2, 2, Asubs) + int_effect; 72 | % data(:, :, 1, 1, Bsubs) = data(:, :, 1, 1, Bsubs) - int_effect; 73 | % data(:, :, 1, 2, Bsubs) = data(:, :, 1, 2, Bsubs) + int_effect; 74 | % data(:, :, 2, 1, Bsubs) = data(:, :, 1, 1, Bsubs) + int_effect; 75 | % data(:, :, 2, 2, Bsubs) = data(:, :, 2, 2, Bsubs) - int_effect; 76 | end 77 | end 78 | 79 | %Calculate ANOVA 80 | %test_results(i) = calc_Fmax(data, cond_subs, dims, n_perm, alpha); 81 | test_results(i) = calc_param_ANOVA(data, cond_subs, dims, alpha, 'none', 'none'); 82 | 83 | end 84 | toc 85 | 86 | 87 | %% Report results 88 | 89 | fprintf('\nRejection rate = %.3f\n', mean(mean([test_results(:).h]))) 90 | fprintf('Mean p = %.3f\n', mean(mean([test_results(:).p]))) 91 | fprintf('Mean F_obs = %.3f\n', mean(mean([test_results(:).F_obs]))) 92 | fprintf('Mean F_crit = %.3f\n', mean(mean([test_results(:).Fmax_crit]))) 93 | fprintf('Parametric F_crit = %.3f\n\n', finv(.95, test_results(1).df(1), test_results(1).df(2))) 94 | -------------------------------------------------------------------------------- /ttest2xls.m: -------------------------------------------------------------------------------- 1 | %Output results of Mass Univariate Toolbox t-test to a spreadsheet. 2 | % 3 | %EXAMPLE USAGE 4 | % >> ttest2xls(GND, 1, 'results.xslx') 5 | % 6 | %REQUIRED INPUTS 7 | % GND - A GND variable with t-test results 8 | % test_id - The test number within the t_tests field of the GND 9 | % struct 10 | % output_fname - The filename for the spreadsheet that will be saved. If 11 | % you don't want to save in the current working directory, 12 | % include a full filepath 13 | %OPTIONAL INPUT 14 | % format_output - A boolean specifying whether to apply formatting to the 15 | % spreadsheet output. {default: true} 16 | % 17 | %VERSION DATE: 18 March 2019 18 | %AUTHOR: Eric Fields 19 | % 20 | %NOTE: This function is provided "as is" and any express or implied warranties 21 | %are disclaimed. 22 | 23 | %Copyright (c) 2017, Eric Fields 24 | %All rights reserved. 25 | %This code is free and open source software made available under the 3-clause BSD license. 26 | 27 | function ttest2xls(GND, test_id, output_fname, format_output) 28 | 29 | %% Set-up 30 | 31 | global VERBLEVEL; 32 | if isempty(VERBLEVEL) 33 | VERBLEVEL = 2; 34 | end 35 | 36 | %Set formatting option if no input 37 | if nargin < 4 38 | if ispc() 39 | format_output = true; 40 | else 41 | format_output = false; 42 | end 43 | end 44 | if format_output && ~ispc() 45 | watchit(sprintf('Spreadsheet formatting on non-Windows systems is buggy.\nSee the FMUT documentation for an explanation and possible workaround.')) 46 | end 47 | 48 | %Define function for writing to spreadsheet and update Java class path 49 | %if necessary 50 | if ispc() 51 | writexls = @xlswrite; 52 | else 53 | %Get full path for POI .jar files not on static Java class path 54 | [~, missing_poi_files] = get_poi_paths(); 55 | %Add POI files to dynamic Java class path if they aren't on the 56 | %static path 57 | if ~isempty(missing_poi_files) 58 | if VERBLEVEL 59 | watchit(sprintf(['POI libraries are not on the static Java class path.\n', ... 60 | 'This can delete global variables and create other problems.\n', ... 61 | 'You can avoid this by running add_poi_path.'])); 62 | end 63 | %Add each .jar file to the dynamic path 64 | add_poi_path('-dynamic'); 65 | end 66 | writexls = @xlwrite; 67 | end 68 | 69 | %Make sure we're not adding sheets to existing file 70 | if exist(output_fname, 'file') 71 | user_resp = questdlg(sprintf('WARNING: %s already exists. Do you want to overwrite it?', output_fname), 'WARNING'); 72 | if strcmp(user_resp, 'No') 73 | return; 74 | else 75 | delete(output_fname) 76 | end 77 | end 78 | 79 | %Create assign t-tests results for easier reference 80 | results = GND.t_tests(test_id); 81 | if isfield(GND, 'group_desc') 82 | n_subs = results.df + 2; %between subjects t-test 83 | else 84 | n_subs = results.df + 1; %one sample t-test 85 | end 86 | 87 | warning('off', 'MATLAB:xlswrite:AddSheet') 88 | 89 | %% Test summary 90 | 91 | summary = {'Study', GND.exp_desc; ... 92 | 'GND', [GND.filepath GND.filename]; ... 93 | 'Bin', results.bin; ... 94 | 'Bin Description', GND.bin_info(results.bin).bindesc; ... 95 | 'Time Window', sprintf('%.0f - %.0f', round(results.time_wind(:))); ... 96 | 'Mean window', results.mean_wind; ... 97 | 'Electrodes', [sprintf('%s, ', results.include_chans{1:end-1}), results.include_chans{end}]; ... 98 | 'Multiple comparisons correction method', results.mult_comp_method; ... 99 | 'Number of permutations', results.n_perm; ... 100 | 'Alpha or q(FDR)', results.desired_alphaORq; ... 101 | '# subjects', n_subs}; 102 | if ~strcmpi(results.mult_comp_method, 'cluster mass perm test') 103 | summary(end+1, :) = {'t critical value', results.crit_t(2)}; 104 | end 105 | writexls(output_fname, summary, 'test summary'); 106 | 107 | %% Cluster summary 108 | 109 | if strcmpi(results.mult_comp_method, 'cluster mass perm test') 110 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 111 | test_tobs = results.data_t; 112 | else 113 | test_tobs = GND.grands_t(results.used_chan_ids, results.used_tpt_ids, results.bin); 114 | end 115 | clust_sum = cell(11, 2); 116 | clust_sum{1, 1} = GND.bin_info(results.bin).bindesc; 117 | clust_sum{3, 1} = 'POSITIVE CLUSTERS'; 118 | clust_sum{3, 4} = 'NEGATIVE CLUSTERS'; 119 | for clust_type = 1:2 120 | row = 5; 121 | if clust_type == 1 122 | col = 1; 123 | num_clusters = length(results.clust_info.pos_clust_pval); 124 | elseif clust_type == 2 125 | col = 4; 126 | num_clusters = length(results.clust_info.neg_clust_pval); 127 | end 128 | for cluster = 1:num_clusters 129 | %labels 130 | clust_sum(row:row+8, col) = {sprintf('CLUSTER %d', cluster); ... 131 | 'cluster mass'; 'p-value'; 'spatial extent'; ... 132 | 'temporal extent'; 'spatial peak'; 'temporal peak'; ... 133 | 'spatial mass peak'; 'temporal mass peak'}; 134 | %assign cluster mass and p-value; 135 | %get array of t-values in cluster 136 | if clust_type == 1 137 | clust_sum{row+1, col+1} = results.clust_info.pos_clust_mass(cluster); 138 | clust_sum{row+2, col+1} = results.clust_info.pos_clust_pval(cluster); 139 | clust_tobs = test_tobs; 140 | clust_tobs(results.clust_info.pos_clust_ids ~= cluster) = 0; 141 | elseif clust_type == 2 142 | clust_sum{row+1, col+1} = results.clust_info.neg_clust_mass(cluster); 143 | clust_sum{row+2, col+1} = results.clust_info.neg_clust_pval(cluster); 144 | clust_tobs = test_tobs; 145 | clust_tobs(results.clust_info.neg_clust_ids ~= cluster) = 0; 146 | end 147 | %spatial extent 148 | clust_sum{row+3, col+1} = sprintf('%s, ', results.include_chans{any(clust_tobs, 2)}); 149 | %temporal extent 150 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 151 | clust_sum{row+4, col+1} = sprintf('Mean window: %.0f - %.0f', results.time_wind(1), results.time_wind(2)); 152 | else 153 | clust_sum{row+4, col+1} = sprintf('%.0f - %.0f', ... 154 | GND.time_pts(min(results.used_tpt_ids(any(clust_tobs, 1)))), ... 155 | GND.time_pts(max(results.used_tpt_ids(any(clust_tobs, 1))))); 156 | end 157 | %Spatial and temporal peak 158 | [max_elec, max_timept] = find(abs(clust_tobs) == max(abs(clust_tobs(:)))); %find location of max F in cluster 159 | clust_sum{row+5, col+1} = results.include_chans{max_elec}; 160 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 161 | clust_sum{row+6, col+1} = sprintf('Mean window: %.0f - %.0f', results.time_wind(1), results.time_wind(2)); 162 | else 163 | clust_sum{row+6, col+1} = sprintf('%.0f', GND.time_pts(results.used_tpt_ids(max_timept))); 164 | end 165 | %Spatial and temporal center (collapsed across the other 166 | %dimension) 167 | [~, max_elec_clust] = max(abs(sum(clust_tobs, 2))); 168 | clust_sum{row+7, col+1} = results.include_chans{max_elec_clust}; 169 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 170 | clust_sum{row+8, col+1} = sprintf('Mean window: %.0f - %.0f', results.time_wind(1), results.time_wind(2)); 171 | else 172 | [~, max_time_clust] = max(abs(sum(clust_tobs, 1))); 173 | clust_sum{row+8, col+1} = sprintf('%.0f', GND.time_pts(results.used_tpt_ids(max_time_clust))); 174 | end 175 | row = row+10; 176 | end 177 | end 178 | writexls(output_fname, clust_sum, 'cluster summary'); 179 | end 180 | 181 | %% cluster IDs, t obs, p-values for each effect 182 | 183 | %Create headers 184 | chan_header = [' '; results.include_chans']; 185 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 186 | time_header = cell(1, size(results.time_wind, 1)); 187 | for t = 1:size(results.time_wind, 1) 188 | time_header{t} = sprintf('%.0f - %.0f', results.time_wind(t,1), results.time_wind(t,2)); 189 | end 190 | else 191 | time_header = num2cell(GND.time_pts(results.used_tpt_ids)); 192 | end 193 | 194 | %Cluster_ids 195 | if strcmpi(results.mult_comp_method, 'cluster mass perm test') 196 | pos_clust_ids = [chan_header, [time_header; num2cell(results.clust_info.pos_clust_ids)]]; 197 | neg_clust_ids = [chan_header, [time_header; num2cell(results.clust_info.neg_clust_ids)]]; 198 | writexls(output_fname, pos_clust_ids, 'pos_clust_IDs'); 199 | writexls(output_fname, neg_clust_ids, 'neg_clust_IDs'); 200 | end 201 | 202 | %t observed 203 | if strcmpi(results.mean_wind, 'yes') || strcmpi(results.mean_wind, 'y') 204 | t_obs = num2cell(results.data_t); 205 | else 206 | t_obs = num2cell(GND.grands_t(results.used_chan_ids, results.used_tpt_ids, results.bin)); 207 | end 208 | t_obs_table = [chan_header, [time_header; t_obs]]; 209 | writexls(output_fname, t_obs_table, 't_obs'); 210 | 211 | %p-values 212 | if ~strcmpi(results.mult_comp_method, 'bky') 213 | adj_pvals = [chan_header, [time_header; num2cell(results.adj_pval)]]; 214 | writexls(output_fname, adj_pvals, 'adj_pvals'); 215 | end 216 | 217 | %Try to format 218 | if format_output 219 | try 220 | format_xls(output_fname) 221 | catch ME 222 | watchit(sprintf('Could not format spreadsheet because of the following error:\n%s', ME.message)) 223 | end 224 | end 225 | 226 | end 227 | -------------------------------------------------------------------------------- /xlwrite.m: -------------------------------------------------------------------------------- 1 | function [status, message]=xlwrite(filename,A,sheet, range) 2 | % XLWRITE Write to Microsoft Excel spreadsheet file using Java 3 | % XLWRITE(FILE,ARRAY) writes ARRAY to the first worksheet in the Excel 4 | % file named FILE, starting at cell A1. It aims to have exactly the same 5 | % behaviour as XLSWRITE. See also XLSWRITE. 6 | % 7 | % XLWRITE(FILE,ARRAY,SHEET) writes to the specified worksheet. 8 | % 9 | % XLWRITE(FILE,ARRAY,RANGE) writes to the rectangular region 10 | % specified by RANGE in the first worksheet of the file. Specify RANGE 11 | % using the syntax 'C1:C2', where C1 and C2 are opposing corners of the 12 | % region. 13 | % 14 | % XLWRITE(FILE,ARRAY,SHEET,RANGE) writes to the specified SHEET and 15 | % RANGE. 16 | % 17 | % STATUS = XLWRITE(FILE,ARRAY,SHEET,RANGE) returns the completion 18 | % status of the write operation: TRUE (logical 1) for success, FALSE 19 | % (logical 0) for failure. Inputs SHEET and RANGE are optional. 20 | % 21 | % Input Arguments: 22 | % 23 | % FILE String that specifies the file to write. If the file does not 24 | % exist, XLWRITE creates a file, determining the format based on 25 | % the specified extension. To create a file compatible with Excel 26 | % 97-2003 software, specify an extension of '.xls'. If you do not 27 | % specify an extension, XLWRITE applies '.xls'. 28 | % ARRAY Two-dimensional logical, numeric or character array or, if each 29 | % cell contains a single element, a cell array. 30 | % SHEET Worksheet to write. One of the following: 31 | % * String that contains the worksheet name. 32 | % * Positive, integer-valued scalar indicating the worksheet 33 | % index. 34 | % If SHEET does not exist, XLWRITE adds a new sheet at the end 35 | % of the worksheet collection. 36 | % RANGE String that specifies a rectangular portion of the worksheet to 37 | % read. Not case sensitive. Use Excel A1 reference style. 38 | % * If you specify a SHEET, RANGE can either fit the size of 39 | % ARRAY or specify only the first cell (such as 'D2'). 40 | % * If you do not specify a SHEET, RANGE must include both 41 | % corners and a colon character (:), even for a single cell 42 | % (such as 'D2:D2'). 43 | % * If RANGE is larger than the size of ARRAY, Excel fills the 44 | % remainder of the region with #N/A. If RANGE is smaller than 45 | % the size of ARRAY, XLWRITE writes only the subset that fits 46 | % into RANGE to the file. 47 | % 48 | % Note 49 | % * This function requires the POI library to be in your javapath. 50 | % To add the Apache POI Library execute commands: 51 | % (This assumes the POI lib files are in folder 'poi_library') 52 | % javaaddpath('poi_library/poi-3.8-20120326.jar'); 53 | % javaaddpath('poi_library/poi-ooxml-3.8-20120326.jar'); 54 | % javaaddpath('poi_library/poi-ooxml-schemas-3.8-20120326.jar'); 55 | % javaaddpath('poi_library/xmlbeans-2.3.0.jar'); 56 | % javaaddpath('poi_library/dom4j-1.6.1.jar'); 57 | % * Excel converts Inf values to 65535. XLWRITE converts NaN values to 58 | % empty cells. 59 | % 60 | % EXAMPLES 61 | % % Write a 7-element vector to testdata.xls: 62 | % xlwrite('testdata.xls', [12.7, 5.02, -98, 63.9, 0, -.2, 56]) 63 | % 64 | % % Write mixed text and numeric data to testdata2.xls 65 | % % starting at cell E1 of Sheet1: 66 | % d = {'Time','Temperature'; 12,98; 13,99; 14,97}; 67 | % xlwrite('testdata2.xls', d, 1, 'E1') 68 | % 69 | % 70 | % REVISIONS 71 | % 20121004 - First version using JExcelApi 72 | % 20121101 - Modified to use POI library instead of JExcelApi (allows to 73 | % generate XLSX) 74 | % 20121127 - Fixed bug: use existing rows if present, instead of 75 | % overwrite rows by default. Thanks to Dan & Jason. 76 | % 20121204 - Fixed bug: if a numeric sheet is given & didn't exist, 77 | % an error was returned instead of creating the sheet. Thanks to Marianna 78 | % 20130106 - Fixed bug: use existing cell if present, instead of 79 | % overwriting. This way original XLS formatting is kept & not 80 | % overwritten. 81 | % 20130125 - Fixed bug & documentation. Incorrect working of NaN. Thanks Klaus 82 | % 20130227 - Fixed bug when no sheet number given & added Stax to java 83 | % load. Thanks to Thierry 84 | % 85 | % Copyright 2012-2013, Alec de Zegher 86 | %============================================================================== 87 | 88 | % Check if POI lib is loaded 89 | if exist('org.apache.poi.ss.usermodel.WorkbookFactory', 'class') ~= 8 ... 90 | || exist('org.apache.poi.hssf.usermodel.HSSFWorkbook', 'class') ~= 8 ... 91 | || exist('org.apache.poi.xssf.usermodel.XSSFWorkbook', 'class') ~= 8 92 | 93 | error('xlWrite:poiLibsNotLoaded',... 94 | 'The POI library is not loaded in Matlab.\nCheck that POI jar files are in Matlab Java path!'); 95 | end 96 | 97 | % Import required POI Java Classes 98 | import org.apache.poi.ss.usermodel.*; 99 | import org.apache.poi.hssf.usermodel.*; 100 | import org.apache.poi.xssf.usermodel.*; 101 | import org.apache.poi.ss.usermodel.*; 102 | 103 | import org.apache.poi.ss.util.*; 104 | 105 | status=0; 106 | 107 | % If no sheet & xlrange is defined, attribute an empty value to it 108 | if nargin < 3; sheet = []; end 109 | if nargin < 4; range = []; end 110 | 111 | % Check if sheetvariable contains range data 112 | if nargin < 4 && ~isempty(strfind(sheet,':')) 113 | range = sheet; 114 | sheet = []; 115 | end 116 | 117 | % check if input data is given 118 | if isempty(A) 119 | error('xlwrite:EmptyInput', 'Input array is empty!'); 120 | end 121 | % Check that input data is not bigger than 2D 122 | if ndims(A) > 2 123 | error('xlwrite:InputDimension', ... 124 | 'Dimension of input array should not be higher than two.'); 125 | end 126 | 127 | % Set java path to same path as Matlab path 128 | java.lang.System.setProperty('user.dir', pwd); 129 | 130 | % Open a file 131 | xlsFile = java.io.File(filename); 132 | 133 | % If file does not exist create a new workbook 134 | if xlsFile.isFile() 135 | % create XSSF or HSSF workbook from existing workbook 136 | fileIn = java.io.FileInputStream(xlsFile); 137 | xlsWorkbook = WorkbookFactory.create(fileIn); 138 | else 139 | % Create a new workbook based on the extension. 140 | [~,~,fileExt] = fileparts(filename); 141 | 142 | % Check based on extension which type to create. If no (valid) 143 | % extension is given, create XLSX file 144 | switch lower(fileExt) 145 | case '.xls' 146 | xlsWorkbook = HSSFWorkbook(); 147 | case '.xlsx' 148 | xlsWorkbook = XSSFWorkbook(); 149 | otherwise 150 | xlsWorkbook = XSSFWorkbook(); 151 | 152 | % Also update filename with added extension 153 | filename = [filename '.xlsx']; 154 | end 155 | end 156 | 157 | % If sheetname given, enter data in this sheet 158 | if ~isempty(sheet) 159 | if isnumeric(sheet) 160 | % Java uses 0-indexing, so take sheetnumer-1 161 | % Check if the sheet can exist 162 | if xlsWorkbook.getNumberOfSheets() >= sheet && sheet >= 1 163 | xlsSheet = xlsWorkbook.getSheetAt(sheet-1); 164 | else 165 | % There are less number of sheets, that the requested sheet, so 166 | % return an empty sheet 167 | xlsSheet = []; 168 | end 169 | else 170 | xlsSheet = xlsWorkbook.getSheet(sheet); 171 | end 172 | 173 | % Create a new sheet if it is empty 174 | if isempty(xlsSheet) 175 | %warning('xlwrite:AddSheet', 'Added specified worksheet.'); %ECF COMMENTED OUT 6/15/17 176 | 177 | % Add the sheet 178 | if isnumeric(sheet) 179 | xlsSheet = xlsWorkbook.createSheet(['Sheet ' num2str(sheet)]); 180 | else 181 | % Create a safe sheet name 182 | sheet = WorkbookUtil.createSafeSheetName(sheet); 183 | xlsSheet = xlsWorkbook.createSheet(sheet); 184 | end 185 | end 186 | 187 | else 188 | % check number of sheets 189 | nSheets = xlsWorkbook.getNumberOfSheets(); 190 | 191 | % If no sheets, create one 192 | if nSheets < 1 193 | xlsSheet = xlsWorkbook.createSheet('Sheet 1'); 194 | else 195 | % Select the first sheet 196 | xlsSheet = xlsWorkbook.getSheetAt(0); 197 | end 198 | end 199 | 200 | % if range is not specified take start row & col at A1 201 | % locations are 0 indexed 202 | if isempty(range) 203 | iRowStart = 0; 204 | iColStart = 0; 205 | iRowEnd = size(A, 1)-1; 206 | iColEnd = size(A, 2)-1; 207 | else 208 | % Split range in start & end cell 209 | iSeperator = strfind(range, ':'); 210 | if isempty(iSeperator) 211 | % Only start was defined as range 212 | % Create a helper to get the row and column 213 | cellStart = CellReference(range); 214 | iRowStart = cellStart.getRow(); 215 | iColStart = cellStart.getCol(); 216 | % End column calculated based on size of A 217 | iRowEnd = iRowStart + size(A, 1)-1; 218 | iColEnd = iColStart + size(A, 2)-1; 219 | else 220 | % Define start & end cell 221 | cellStart = range(1:iSeperator-1); 222 | cellEnd = range(iSeperator+1:end); 223 | 224 | % Create a helper to get the row and column 225 | cellStart = CellReference(cellStart); 226 | cellEnd = CellReference(cellEnd); 227 | 228 | % Get start & end locations 229 | iRowStart = cellStart.getRow(); 230 | iColStart = cellStart.getCol(); 231 | iRowEnd = cellEnd.getRow(); 232 | iColEnd = cellEnd.getCol(); 233 | end 234 | end 235 | 236 | % Get number of elements in A (0-indexed) 237 | nRowA = size(A, 1)-1; 238 | nColA = size(A, 2)-1; 239 | 240 | % If data is a cell, convert it 241 | if ~iscell(A) 242 | A = num2cell(A); 243 | end 244 | 245 | % Iterate over all data 246 | for iRow = iRowStart:iRowEnd 247 | % Fetch the row (if it exists) 248 | currentRow = xlsSheet.getRow(iRow); 249 | if isempty(currentRow) 250 | % Create a new row, as it does not exist yet 251 | currentRow = xlsSheet.createRow(iRow); 252 | end 253 | 254 | % enter data for all cols 255 | for iCol = iColStart:iColEnd 256 | % Check if cell exists 257 | currentCell = currentRow.getCell(iCol); 258 | if isempty(currentCell) 259 | % Create a new cell, as it does not exist yet 260 | currentCell = currentRow.createCell(iCol); 261 | end 262 | 263 | % Check if we are still in array A 264 | if (iRow-iRowStart)<=nRowA && (iCol-iColStart)<=nColA 265 | % Fetch the data 266 | data = A{iRow-iRowStart+1, iCol-iColStart+1}; 267 | 268 | if ~isempty(data) 269 | % if it is a NaN value, convert it to an empty string 270 | if isnumeric(data) && isnan(data) 271 | data = ''; 272 | end 273 | 274 | % Write data to cell 275 | currentCell.setCellValue(data); 276 | end 277 | 278 | else 279 | % Set field to NA 280 | currentCell.setCellErrorValue(FormulaError.NA.getCode()); 281 | end 282 | end 283 | end 284 | 285 | % Write & close the workbook 286 | fileOut = java.io.FileOutputStream(filename); 287 | xlsWorkbook.write(fileOut); 288 | fileOut.close(); 289 | 290 | status = 1; 291 | 292 | end -------------------------------------------------------------------------------- /xlwrite_license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Alec de Zegher 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | --------------------------------------------------------------------------------