Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1''' 

2Bland-Altman mean-difference plots 

3 

4Author: Joses Ho 

5License: BSD-3 

6''' 

7 

8import numpy as np 

9from . import utils 

10 

11 

12def mean_diff_plot(m1, m2, sd_limit=1.96, ax=None, scatter_kwds=None, 

13 mean_line_kwds=None, limit_lines_kwds=None): 

14 """ 

15 Construct a Tukey/Bland-Altman Mean Difference Plot. 

16 

17 Tukey's Mean Difference Plot (also known as a Bland-Altman plot) is a 

18 graphical method to analyze the differences between two methods of 

19 measurement. The mean of the measures is plotted against their difference. 

20 

21 For more information see 

22 https://en.wikipedia.org/wiki/Bland-Altman_plot 

23 

24 Parameters 

25 ---------- 

26 m1 : array_like 

27 A 1-d array. 

28 m2 : array_like 

29 A 1-d array. 

30 sd_limit : float 

31 The limit of agreements expressed in terms of the standard deviation of 

32 the differences. If `md` is the mean of the differences, and `sd` is 

33 the standard deviation of those differences, then the limits of 

34 agreement that will be plotted are md +/- sd_limit * sd. 

35 The default of 1.96 will produce 95% confidence intervals for the means 

36 of the differences. If sd_limit = 0, no limits will be plotted, and 

37 the ylimit of the plot defaults to 3 standard deviations on either 

38 side of the mean. 

39 ax : AxesSubplot 

40 If `ax` is None, then a figure is created. If an axis instance is 

41 given, the mean difference plot is drawn on the axis. 

42 scatter_kwds : dict 

43 Options to to style the scatter plot. Accepts any keywords for the 

44 matplotlib Axes.scatter plotting method 

45 mean_line_kwds : dict 

46 Options to to style the scatter plot. Accepts any keywords for the 

47 matplotlib Axes.axhline plotting method 

48 limit_lines_kwds : dict 

49 Options to to style the scatter plot. Accepts any keywords for the 

50 matplotlib Axes.axhline plotting method 

51 

52 Returns 

53 ------- 

54 Figure 

55 If `ax` is None, the created figure. Otherwise the figure to which 

56 `ax` is connected. 

57 

58 References 

59 ---------- 

60 Bland JM, Altman DG (1986). "Statistical methods for assessing agreement 

61 between two methods of clinical measurement" 

62 

63 Examples 

64 -------- 

65 

66 Load relevant libraries. 

67 

68 >>> import statsmodels.api as sm 

69 >>> import numpy as np 

70 >>> import matplotlib.pyplot as plt 

71 

72 Making a mean difference plot. 

73 

74 >>> # Seed the random number generator. 

75 >>> # This ensures that the results below are reproducible. 

76 >>> np.random.seed(9999) 

77 >>> m1 = np.random.random(20) 

78 >>> m2 = np.random.random(20) 

79 >>> f, ax = plt.subplots(1, figsize = (8,5)) 

80 >>> sm.graphics.mean_diff_plot(m1, m2, ax = ax) 

81 >>> plt.show() 

82 

83 .. plot:: plots/graphics-mean_diff_plot.py 

84 """ 

85 fig, ax = utils.create_mpl_ax(ax) 

86 

87 if len(m1) != len(m2): 

88 raise ValueError('m1 does not have the same length as m2.') 

89 if sd_limit < 0: 

90 raise ValueError('sd_limit ({}) is less than 0.'.format(sd_limit)) 

91 

92 means = np.mean([m1, m2], axis=0) 

93 diffs = m1 - m2 

94 mean_diff = np.mean(diffs) 

95 std_diff = np.std(diffs, axis=0) 

96 

97 scatter_kwds = scatter_kwds or {} 

98 if 's' not in scatter_kwds: 

99 scatter_kwds['s'] = 20 

100 mean_line_kwds = mean_line_kwds or {} 

101 limit_lines_kwds = limit_lines_kwds or {} 

102 for kwds in [mean_line_kwds, limit_lines_kwds]: 

103 if 'color' not in kwds: 

104 kwds['color'] = 'gray' 

105 if 'linewidth' not in kwds: 

106 kwds['linewidth'] = 1 

107 if 'linestyle' not in mean_line_kwds: 

108 kwds['linestyle'] = '--' 

109 if 'linestyle' not in limit_lines_kwds: 

110 kwds['linestyle'] = ':' 

111 

112 ax.scatter(means, diffs, **scatter_kwds) # Plot the means against the diffs. 

113 ax.axhline(mean_diff, **mean_line_kwds) # draw mean line. 

114 

115 # Annotate mean line with mean difference. 

116 ax.annotate('mean diff:\n{}'.format(np.round(mean_diff, 2)), 

117 xy=(0.99, 0.5), 

118 horizontalalignment='right', 

119 verticalalignment='center', 

120 fontsize=14, 

121 xycoords='axes fraction') 

122 

123 if sd_limit > 0: 

124 half_ylim = (1.5 * sd_limit) * std_diff 

125 ax.set_ylim(mean_diff - half_ylim, 

126 mean_diff + half_ylim) 

127 limit_of_agreement = sd_limit * std_diff 

128 lower = mean_diff - limit_of_agreement 

129 upper = mean_diff + limit_of_agreement 

130 for j, lim in enumerate([lower, upper]): 

131 ax.axhline(lim, **limit_lines_kwds) 

132 ax.annotate('-SD{}: {}'.format(sd_limit, np.round(lower, 2)), 

133 xy=(0.99, 0.07), 

134 horizontalalignment='right', 

135 verticalalignment='bottom', 

136 fontsize=14, 

137 xycoords='axes fraction') 

138 ax.annotate('+SD{}: {}'.format(sd_limit, np.round(upper, 2)), 

139 xy=(0.99, 0.92), 

140 horizontalalignment='right', 

141 fontsize=14, 

142 xycoords='axes fraction') 

143 

144 elif sd_limit == 0: 

145 half_ylim = 3 * std_diff 

146 ax.set_ylim(mean_diff - half_ylim, 

147 mean_diff + half_ylim) 

148 

149 ax.set_ylabel('Difference', fontsize=15) 

150 ax.set_xlabel('Means', fontsize=15) 

151 ax.tick_params(labelsize=13) 

152 fig.tight_layout() 

153 return fig