sigstar.m 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. function varargout=sigstar(groups,stats,esize,nosort)
  2. % sigstar - Add significance stars to bar charts, boxplots, line charts, etc,
  3. %
  4. % H = sigstar(groups,stats,nsort)
  5. %
  6. % Purpose
  7. % Add stars and lines highlighting significant differences between pairs of groups.
  8. % The user specifies the groups and associated p-values. The function handles much of
  9. % the placement and drawing of the highlighting. Stars are drawn according to:
  10. % * represents p<=0.05
  11. % ** represents p<=1E-2
  12. % *** represents p<=1E-3
  13. %
  14. %
  15. % Inputs
  16. % groups - a cell array defining the pairs of groups to compare. Groups defined
  17. % either as pairs of scalars indicating locations along the X axis or as
  18. % strings corresponding to X-tick labels. Groups can be a mixture of both
  19. % definition types.
  20. % stats - a vector of p-values the same length as groups. If empty or missing it's
  21. % assumed to be a vector of 0.05s the same length as groups. Nans are treated
  22. % as indicating non-significance.
  23. % nsort - optional, 0 by default. If 1, then significance markers are plotted in
  24. % the order found in groups. If 0, then they're sorted by the length of the
  25. % bar.
  26. %
  27. % Outputs
  28. % H - optionally return handles for significance highlights. Each row is a different
  29. % highlight bar. The first column is the line. The second column is the text (stars).
  30. %
  31. %
  32. % Examples
  33. % 1.
  34. % bar([5,2,1.5])
  35. % sigstar({[1,2], [1,3]})
  36. %
  37. % 2.
  38. % bar([5,2,1.5])
  39. % sigstar({[2,3],[1,2], [1,3]},[nan,0.05,0.05])
  40. %
  41. % 3. **DOESN'T WORK IN 2014b**
  42. % R=randn(30,2);
  43. % R(:,1)=R(:,1)+3;
  44. % boxplot(R)
  45. % set(gca,'XTick',1:2,'XTickLabel',{'A','B'})
  46. % H=sigstar({{'A','B'}},0.01);
  47. % ylim([-3,6.5])
  48. % set(H,'color','r')
  49. %
  50. % 4. Note the difference in the order with which we define the groups in the
  51. % following two cases.
  52. % x=[1,2,3,2,1];
  53. % subplot(1,2,1)
  54. % bar(x)
  55. % sigstar({[1,2], [2,3], [4,5]})
  56. % subplot(1,2,2)
  57. % bar(x)
  58. % sigstar({[2,3],[1,2], [4,5]})
  59. %
  60. % ALSO SEE: demo_sigstar
  61. %
  62. % KNOWN ISSUES:
  63. % 1. Algorithm for identifying whether significance bar will overlap with
  64. % existing plot elements may not work in some cases (see line 277)
  65. % 2. Bars may not look good on exported graphics with small page sizes.
  66. % Simply increasing the width and height of the graph with the
  67. % PaperPosition property of the current figure should fix things.
  68. %
  69. % Rob Campbell - CSHL 2013
  70. %Input argument error checking
  71. %If the user entered just one group pair and forgot to wrap it in a cell array
  72. %then we'll go easy on them and wrap it here rather then generate an error
  73. if ~iscell(groups) & length(groups)==2
  74. groups={groups};
  75. end
  76. if nargin<3
  77. stats=repmat(0.05,1,length(groups));
  78. end
  79. if isempty(stats)
  80. stats=repmat(0.05,1,length(groups));
  81. end
  82. if nargin<4
  83. nosort=0;
  84. end
  85. %Check the inputs are of the right sort
  86. if ~iscell(groups)
  87. error('groups must be a cell array')
  88. end
  89. if ~isvector(stats)
  90. error('stats must be a vector')
  91. end
  92. if length(stats)~=length(groups)
  93. error('groups and stats must be the same length')
  94. end
  95. %Each member of the cell array groups may be one of three things:
  96. %1. A pair of indices.
  97. %2. A pair of strings (in cell array) referring to X-Tick labels
  98. %3. A cell array containing one index and one string
  99. %
  100. % For our function to run, we will need to convert all of these into pairs of
  101. % indices. Here we loop through groups and do this.
  102. xlocs=nan(length(groups),2); %matrix that will store the indices
  103. xtl=get(gca,'XTickLabel');
  104. for ii=1:length(groups)
  105. grp=groups{ii};
  106. if isnumeric(grp)
  107. xlocs(ii,:)=grp; %Just store the indices if they're the right format already
  108. elseif iscell(grp) %Handle string pairs or string/index pairs
  109. if isstr(grp{1})
  110. a=strmatch(grp{1},xtl);
  111. elseif isnumeric(grp{1})
  112. a=grp{1};
  113. end
  114. if isstr(grp{2})
  115. b=strmatch(grp{2},xtl);
  116. elseif isnumeric(grp{2})
  117. b=grp{2};
  118. end
  119. xlocs(ii,:)=[a,b];
  120. end
  121. %Ensure that the first column is always smaller number than the second
  122. xlocs(ii,:)=sort(xlocs(ii,:));
  123. end
  124. %If there are any NaNs we have messed up.
  125. if any(isnan(xlocs(:)))
  126. error('Some groups were not found')
  127. end
  128. %Optionally sort sig bars from shortest to longest so we plot the shorter ones first
  129. %in the loop below. Usually this will result in the neatest plot. If we waned to
  130. %optimise the order the sig bars are plotted to produce the neatest plot, then this
  131. %is where we'd do it. Not really worth the effort, though, as few plots are complicated
  132. %enough to need this and the user can define the order very easily at the command line.
  133. if ~nosort
  134. [~,ind]=sort(xlocs(:,2)-xlocs(:,1),'ascend');
  135. xlocs=xlocs(ind,:);groups=groups(ind);
  136. stats=stats(ind);
  137. esize=esize(ind);
  138. end
  139. %-----------------------------------------------------
  140. %Add the sig bar lines and asterisks
  141. holdstate=ishold;
  142. hold on
  143. H=ones(length(groups),2); %The handles will be stored here
  144. y=ylim;
  145. yd=myRange(y)*0.1; %separate sig bars vertically by 5%
  146. for ii=1:length(groups)
  147. thisY=findMinY(xlocs(ii,:))+yd;
  148. H(ii,:)=makeSignificanceBar(xlocs(ii,:),thisY,stats(ii),round(esize(ii),2));
  149. end
  150. %-----------------------------------------------------
  151. %Now we can add the little downward ticks on the ends of each line. We are
  152. %being extra cautious and leaving this it to the end just in case the y limits
  153. %of the graph have changed as we add the highlights. The ticks are set as a
  154. %proportion of the y axis range and we want them all to be the same the same
  155. %for all bars.
  156. yd=myRange(ylim)*0.01; %Ticks are 1% of the y axis range
  157. for ii=1:length(groups)
  158. y=get(H(ii,1),'YData');
  159. y(1)=y(1)-yd;
  160. y(4)=y(4)-yd;
  161. set(H(ii,1),'YData',y)
  162. end
  163. %Be neat and return hold state to whatever it was before we started
  164. if ~holdstate
  165. hold off
  166. elseif holdstate
  167. hold on
  168. end
  169. %Optionally return the handles to the plotted significance bars (first column of H)
  170. %and asterisks (second column of H).
  171. if nargout>0
  172. varargout{1}=H;
  173. end
  174. end %close sigstar
  175. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  176. %Internal functions
  177. function H=makeSignificanceBar(x,y,p,es)
  178. %makeSignificanceBar produces the bar and defines how many asterisks we get for a
  179. %given p-value
  180. if p<=1E-3
  181. stars = {num2str(es),'***'};
  182. offset = 0.03;
  183. elseif p<=1E-2
  184. stars = {num2str(es),'**'};
  185. offset = 0.03;
  186. elseif p<=0.05
  187. stars = {num2str(es),'*'};
  188. offset = 0.03;
  189. else
  190. stars='n.s.';
  191. offset = 0.03;
  192. end
  193. x=repmat(x,2,1);
  194. y=repmat(y,4,1);
  195. H(1)=plot(x(:),y,'-k','Linewidth',2,'Tag','sigstar_bar');
  196. %Increase offset between line and text if we will print "n.s."
  197. %instead of a star.
  198. % offset = 0.025;
  199. % switch stars
  200. % case {'*','**','***'}
  201. % offset = 0.015;
  202. % case {'n.s.'}
  203. % offset = 0.055;
  204. % end
  205. starY=mean(y)+myRange(ylim)*offset;
  206. H(2)=text(mean(x(:)),starY,stars,...
  207. 'HorizontalAlignment','center',...
  208. 'BackGroundColor','none',...
  209. 'Tag','sigstar_stars');
  210. Y=ylim;
  211. if Y(2)<starY*1.1
  212. ylim([Y(1),starY+myRange(Y)*0.1])
  213. end
  214. end %close makeSignificanceBar
  215. function Y=findMinY(x)
  216. % The significance bar needs to be plotted a reasonable distance above all the data points
  217. % found over a particular range of X values. So we need to find these data and calculat the
  218. % the minimum y value needed to clear all the plotted data present over this given range of
  219. % x values.
  220. %
  221. % This version of the function is a fix from Evan Remington
  222. oldXLim = get(gca,'XLim');
  223. oldYLim = get(gca,'YLim');
  224. axis(gca,'tight')
  225. %increase range of x values by 0.1 to ensure correct y max is used
  226. x(1)=x(1)-0.1;
  227. x(2)=x(2)+0.1;
  228. set(gca,'xlim',x) %Matlab automatically re-tightens y-axis
  229. yLim = get(gca,'YLim'); %Now have max y value of all elements within range.
  230. Y = max(yLim);
  231. axis(gca,'normal')
  232. set(gca,'XLim',oldXLim,'YLim',oldYLim)
  233. end %close findMinY
  234. function rng=myRange(x)
  235. %replacement for stats toolbox range function
  236. rng = max(x) - min(x);
  237. end %close myRange