permutationTest.m 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. % [p, observeddifference, effectsize] = permutationTest(sample1, sample2, permutations [, varargin])
  2. %
  3. % Permutation test (aka randomisation test), testing for a difference
  4. % in means between two samples.
  5. %
  6. % In:
  7. % sample1 - vector of measurements representing one sample
  8. % sample2 - vector of measurements representing a second sample
  9. % permutations - the number of permutations
  10. %
  11. % Optional (name-value pairs):
  12. % sidedness - whether to test one- or two-sided:
  13. % 'both' - test two-sided (default)
  14. % 'smaller' - test one-sided, alternative hypothesis is that
  15. % the mean of sample1 is smaller than the mean of
  16. % sample2
  17. % 'larger' - test one-sided, alternative hypothesis is that
  18. % the mean of sample1 is larger than the mean of
  19. % sample2
  20. % exact - whether or not to run an exact test, in which all possible
  21. % combinations are considered. this is only feasible for
  22. % relatively small sample sizes. the 'permutations' argument
  23. % will be ignored for an exact test. (1|0, default 0)
  24. % plotresult - whether or not to plot the distribution of randomised
  25. % differences, along with the observed difference (1|0,
  26. % default: 0)
  27. % showprogress - whether or not to show a progress bar. if 0, no bar
  28. % is displayed; if showprogress > 0, the bar updates
  29. % every showprogress-th iteration.
  30. %
  31. % Out:
  32. % p - the resulting p-value
  33. % observeddifference - the observed difference between the two
  34. % samples, i.e. mean(sample1) - mean(sample2)
  35. % effectsize - the effect size
  36. %
  37. % Usage example:
  38. % >> permutationTest(rand(1,100), rand(1,100)-.25, 10000, ...
  39. % 'plotresult', 1, 'showprogress', 250)
  40. %
  41. % Copyright 2015-2018 Laurens R Krol
  42. % Team PhyPA, Biological Psychology and Neuroergonomics,
  43. % Berlin Institute of Technology
  44. % 2019-02-01 lrk
  45. % - Added short description
  46. % - Increased the number of bins in the plot
  47. % 2018-03-15 lrk
  48. % - Suppressed initial MATLAB:nchoosek:LargeCoefficient warning
  49. % 2018-03-14 lrk
  50. % - Added exact test
  51. % 2018-01-31 lrk
  52. % - Replaced calls to mean() with nanmean()
  53. % 2017-06-15 lrk
  54. % - Updated waitbar message in first iteration
  55. % 2017-04-04 lrk
  56. % - Added progress bar
  57. % 2017-01-13 lrk
  58. % - Switched to inputParser to parse arguments
  59. % 2016-09-13 lrk
  60. % - Caught potential issue when column vectors were used
  61. % - Improved plot
  62. % 2016-02-17 toz
  63. % - Added plot functionality
  64. % 2015-11-26 First version
  65. % This program is free software: you can redistribute it and/or modify
  66. % it under the terms of the GNU General Public License as published by
  67. % the Free Software Foundation, either version 3 of the License, or
  68. % (at your option) any later version.
  69. %
  70. % This program is distributed in the hope that it will be useful,
  71. % but WITHOUT ANY WARRANTY; without even the implied warranty of
  72. % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  73. % GNU General Public License for more details.
  74. %
  75. % You should have received a copy of the GNU General Public License
  76. % along with this program. If not, see <http://www.gnu.org/licenses/>.
  77. function [p, observeddifference, effectsize] = permutationTest(sample1, sample2, permutations, varargin)
  78. % parsing input
  79. p = inputParser;
  80. addRequired(p, 'sample1', @isnumeric);
  81. addRequired(p, 'sample2', @isnumeric);
  82. addRequired(p, 'permutations', @isnumeric);
  83. addParamValue(p, 'sidedness', 'both', @(x) any(validatestring(x,{'both', 'smaller', 'larger'})));
  84. addParamValue(p, 'exact' , 0, @isnumeric);
  85. addParamValue(p, 'plotresult', 0, @isnumeric);
  86. addParamValue(p, 'showprogress', 0, @isnumeric);
  87. parse(p, sample1, sample2, permutations, varargin{:})
  88. sample1 = p.Results.sample1;
  89. sample2 = p.Results.sample2;
  90. permutations = p.Results.permutations;
  91. sidedness = p.Results.sidedness;
  92. exact = p.Results.exact;
  93. plotresult = p.Results.plotresult;
  94. showprogress = p.Results.showprogress;
  95. % enforcing row vectors
  96. if iscolumn(sample1), sample1 = sample1'; end
  97. if iscolumn(sample2), sample2 = sample2'; end
  98. allobservations = [sample1, sample2];
  99. observeddifference = nanmean(sample1) - nanmean(sample2);
  100. effectsize = observeddifference / nanmean([std(sample1), std(sample2)]);
  101. w = warning('off', 'MATLAB:nchoosek:LargeCoefficient');
  102. if ~exact && permutations > nchoosek(numel(allobservations), numel(sample1))
  103. warning(['the number of permutations (%d) is higher than the number of possible combinations (%d);\n' ...
  104. 'consider running an exact test using the ''exact'' argument'], ...
  105. permutations, nchoosek(numel(allobservations), numel(sample1)));
  106. end
  107. warning(w);
  108. if showprogress, w = waitbar(0, 'Preparing test...', 'Name', 'permutationTest'); end
  109. if exact
  110. % getting all possible combinations
  111. allcombinations = nchoosek(1:numel(allobservations), numel(sample1));
  112. permutations = size(allcombinations, 1);
  113. end
  114. % running test
  115. randomdifferences = zeros(1, permutations);
  116. if showprogress, waitbar(0, w, sprintf('Permutation 1 of %d', permutations), 'Name', 'permutationTest'); end
  117. for n = 1:permutations
  118. if showprogress && mod(n,showprogress) == 0, waitbar(n/permutations, w, sprintf('Permutation %d of %d', n, permutations)); end
  119. % selecting either next combination, or random permutation
  120. if exact, permutation = [allcombinations(n,:), setdiff(1:numel(allobservations), allcombinations(n,:))];
  121. else, permutation = randperm(length(allobservations)); end
  122. % dividing into two samples
  123. randomSample1 = allobservations(permutation(1:length(sample1)));
  124. randomSample2 = allobservations(permutation(length(sample1)+1:length(permutation)));
  125. % saving differences between the two samples
  126. randomdifferences(n) = nanmean(randomSample1) - nanmean(randomSample2);
  127. end
  128. if showprogress, delete(w); end
  129. % getting probability of finding observed difference from random permutations
  130. if strcmp(sidedness, 'both')
  131. p = (length(find(abs(randomdifferences) > abs(observeddifference)))+1) / (permutations+1);
  132. elseif strcmp(sidedness, 'smaller')
  133. p = (length(find(randomdifferences < observeddifference))+1) / (permutations+1);
  134. elseif strcmp(sidedness, 'larger')
  135. p = (length(find(randomdifferences > observeddifference))+1) / (permutations+1);
  136. end
  137. % plotting result
  138. if plotresult
  139. figure;
  140. hist(randomdifferences, 20);
  141. hold on;
  142. xlabel('Random differences');
  143. ylabel('Count')
  144. od = plot(observeddifference, 0, '*r', 'DisplayName', sprintf('Observed difference.\nEffect size: %.2f,\np = %f', effectsize, p));
  145. legend(od);
  146. end
  147. end