From 8bf8653a1ba5b3de02370e7c603b61be38d93d63 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Thu, 28 Nov 2013 21:52:11 -0800 Subject: [PATCH 01/43] REF/TST: split up boxplot function into a calculator (cbook.boxplot_stats) and a drawer (axes.bxp). Existing function now relies on these two functions. --- lib/matplotlib/axes/_axes.py | 422 +++++++++--------- lib/matplotlib/cbook.py | 139 ++++++ .../baseline_images/test_axes/boxplot.pdf | Bin 5162 -> 5231 bytes .../baseline_images/test_axes/boxplot.png | Bin 11891 -> 11065 bytes .../baseline_images/test_axes/boxplot.svg | 204 ++++----- .../test_axes/boxplot_no_inverted_whisker.png | Bin 1889 -> 1775 bytes .../test_axes/boxplot_with_CIarray.png | Bin 2369 -> 1870 bytes .../test_axes/bxp_baseline.png | Bin 0 -> 1955 bytes .../test_axes/bxp_customoutlier.png | Bin 0 -> 2159 bytes .../test_axes/bxp_horizontal.png | Bin 0 -> 2161 bytes .../baseline_images/test_axes/bxp_nocaps.png | Bin 0 -> 1888 bytes .../test_axes/bxp_withmean_custompoint.png | Bin 0 -> 2159 bytes .../test_axes/bxp_withmean_line.png | Bin 0 -> 2096 bytes .../test_axes/bxp_withmean_point.png | Bin 0 -> 2174 bytes .../test_axes/bxp_withnotch.png | Bin 0 -> 5119 bytes lib/matplotlib/tests/test_axes.py | 126 ++++++ lib/matplotlib/tests/test_cbook.py | 96 +++- 17 files changed, 656 insertions(+), 331 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_baseline.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 134e7c469b00..3e3f4f34265e 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2884,245 +2884,221 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, .. plot:: pyplots/boxplot_demo.py """ - def bootstrapMedian(data, N=5000): - # determine 95% confidence intervals of the median - M = len(data) - percentile = [2.5, 97.5] - estimate = np.zeros(N) - for n in range(N): - bsIndex = np.random.random_integers(0, M - 1, M) - bsData = data[bsIndex] - estimate[n] = mlab.prctile(bsData, 50) - CI = mlab.prctile(estimate, percentile) - return CI - - def computeConfInterval(data, med, iq, bootstrap): - if bootstrap is not None: - # Do a bootstrap estimate of notch locations. - # get conf. intervals around median - CI = bootstrapMedian(data, N=bootstrap) - notch_min = CI[0] - notch_max = CI[1] - else: - # Estimate notch locations using Gaussian-based - # asymptotic approximation. - # - # For discussion: McGill, R., Tukey, J.W., - # and Larsen, W.A. (1978) "Variations of - # Boxplots", The American Statistician, 32:12-16. - N = len(data) - notch_min = med - 1.57 * iq / np.sqrt(N) - notch_max = med + 1.57 * iq / np.sqrt(N) - return notch_min, notch_max - - if not self._hold: - self.cla() - holdStatus = self._hold - whiskers, caps, boxes, medians, fliers = [], [], [], [], [] + bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap) + if sym == 'b+': + flierprops = dict(linestyle='none', marker='+', + markeredgecolor='blue') + else: + flierprops = sym - # convert x to a list of vectors - if hasattr(x, 'shape'): - if len(x.shape) == 1: - if hasattr(x[0], 'shape'): - x = list(x) - else: - x = [x, ] - elif len(x.shape) == 2: - nr, nc = x.shape - if nr == 1: - x = [x] - elif nc == 1: - x = [x.ravel()] - else: - x = [x[:, i] for i in xrange(nc)] + # replace medians if necessary: + if usermedians is not None: + if len(usermedians) != len(bxpstats): + medmsg = 'usermedians length not compatible with x' + raise ValueError(medmsg) else: - raise ValueError("input x can have no more than 2 dimensions") - if not hasattr(x[0], '__len__'): - x = [x] - col = len(x) + for stats, med in zip(bxpstats, usermedians): + if med is not None: + stats['med'] = med - # sanitize user-input medians - msg1 = "usermedians must either be a list/tuple or a 1d array" - msg2 = "usermedians' length must be compatible with x" - if usermedians is not None: - if hasattr(usermedians, 'shape'): - if len(usermedians.shape) != 1: - raise ValueError(msg1) - elif usermedians.shape[0] != col: - raise ValueError(msg2) - elif len(usermedians) != col: - raise ValueError(msg2) - - #sanitize user-input confidence intervals - msg1 = "conf_intervals must either be a list of tuples or a 2d array" - msg2 = "conf_intervals' length must be compatible with x" - msg3 = "each conf_interval, if specificied, must have two values" if conf_intervals is not None: - if hasattr(conf_intervals, 'shape'): - if len(conf_intervals.shape) != 2: - raise ValueError(msg1) - elif conf_intervals.shape[0] != col: - raise ValueError(msg2) - elif conf_intervals.shape[1] != 2: - raise ValueError(msg3) + if len(conf_intervals) != len(bxpstats): + cimsg = 'conf_intervals length not compatible with x' + raise ValueError(cimsg) else: - if len(conf_intervals) != col: - raise ValueError(msg2) - for ci in conf_intervals: - if ci is not None and len(ci) != 2: - raise ValueError(msg3) + for stats, ci in zip(bxpstats, conf_intervals): + if ci is not None: + if len(ci) != 2: + cimsg2 = 'each confidence interval must '\ + 'have two values' + raise ValueError(cimsg2) + else: + if ci[0] is not None: + stats['cilo'] = ci[0] + if ci[1] is not None: + stats['cihi'] = ci[1] + + artists = self.bxp(bxpstats, positions=positions, widths=widths, + vert=vert, patch_artist=patch_artist, + shownotches=notch, showmeans=False, + showcaps=True, boxprops=None, + flierprops=flierprops, medianprops=None, + meanprops=None, meanline=False) + return artists + + def bxp(self, bxpstats, positions=None, widths=None, vert=True, + patch_artist=False, shownotches=False, showmeans=False, + showcaps=True, boxprops=None, flierprops=None, + medianprops=None, meanprops=None, meanline=False): + + # lists of artists to be output + whiskers, caps, boxes, medians, means, fliers = [], [], [], [], [], [] + + # plotting properties + if boxprops is None: + boxprops = dict(linestyle='-', color='black') + + if flierprops is None: + flierprops = dict(linestyle='none', marker='+', + markeredgecolor='blue') + + if medianprops is None: + medianprops = dict(linestyle='-', color='blue') + + if meanprops is None: + if meanline: + meanprops = dict(linestyle='--', color='red') + else: + meanprops = dict(linestyle='none', markerfacecolor='red', + marker='s') + + def to_vc(xs, ys): + # convert arguments to verts and codes + verts = [] + #codes = [] + for xi, yi in zip(xs, ys): + verts.append((xi, yi)) + verts.append((0, 0)) # ignored + codes = [mpath.Path.MOVETO] + \ + [mpath.Path.LINETO] * (len(verts) - 2) + \ + [mpath.Path.CLOSEPOLY] + return verts, codes + + def patch_list(xs, ys, **kwargs): + verts, codes = to_vc(xs, ys) + path = mpath.Path(verts, codes) + patch = mpatches.PathPatch(path, **kwargs) + self.add_artist(patch) + return [patch] + + # vertical or horizontal plot? + if vert: + def doplot(*args, **kwargs): + return self.plot(*args, **kwargs) - # get some plot info + def dopatch(xs, ys, **kwargs): + return patch_list(xs, ys, **kwargs) + + else: + def doplot(*args, **kwargs): + shuffled = [] + for i in xrange(0, len(args), 2): + shuffled.extend([args[i + 1], args[i]]) + return self.plot(*shuffled, **kwargs) + + def dopatch(xs, ys, **kwargs): + xs, ys = ys, xs # flip X, Y + return patch_list(xs, ys, **kwargs) + + # input validation + N = len(bxpstats) + datashape_message = "List of boxplot statistics and `{0}` " \ + "value must have same length" if positions is None: - positions = list(xrange(1, col + 1)) + positions = list(xrange(1, N + 1)) + elif len(positions) != N: + raise ValueError(datashape_message.format("positions")) + if widths is None: distance = max(positions) - min(positions) - widths = min(0.15 * max(distance, 1.0), 0.5) - if isinstance(widths, float) or isinstance(widths, int): - widths = np.ones((col,), float) * widths - - # loop through columns, adding each to plot - self.hold(True) - for i, pos in enumerate(positions): - d = np.ravel(x[i]) - row = len(d) - if row == 0: - # no data, skip this position - continue - - # get median and quartiles - q1, med, q3 = mlab.prctile(d, [25, 50, 75]) + widths = [min(0.15 * max(distance, 1.0), 0.5)] * N + elif len(widths) != len(bxpstats): + raise ValueError(datashape_message.format("widths")) - # replace with input medians if available - if usermedians is not None: - if usermedians[i] is not None: - med = usermedians[i] - - # get high extreme - iq = q3 - q1 - hi_val = q3 + whis * iq - wisk_hi = np.compress(d <= hi_val, d) - if len(wisk_hi) == 0 or np.max(wisk_hi) < q3: - wisk_hi = q3 - else: - wisk_hi = max(wisk_hi) - - # get low extreme - lo_val = q1 - whis * iq - wisk_lo = np.compress(d >= lo_val, d) - if len(wisk_lo) == 0 or np.min(wisk_lo) > q1: - wisk_lo = q1 - else: - wisk_lo = min(wisk_lo) - - # get fliers - if we are showing them - flier_hi = [] - flier_lo = [] - flier_hi_x = [] - flier_lo_x = [] - if len(sym) != 0: - flier_hi = np.compress(d > wisk_hi, d) - flier_lo = np.compress(d < wisk_lo, d) - flier_hi_x = np.ones(flier_hi.shape[0]) * pos - flier_lo_x = np.ones(flier_lo.shape[0]) * pos - - # get x locations for fliers, whisker, whisker cap and box sides - box_x_min = pos - widths[i] * 0.5 - box_x_max = pos + widths[i] * 0.5 - - wisk_x = np.ones(2) * pos - - cap_x_min = pos - widths[i] * 0.25 - cap_x_max = pos + widths[i] * 0.25 - cap_x = [cap_x_min, cap_x_max] - - # get y location for median - med_y = [med, med] - - # calculate 'notch' plot - if notch: - # conf. intervals from user, if available - if (conf_intervals is not None and - conf_intervals[i] is not None): - notch_max = np.max(conf_intervals[i]) - notch_min = np.min(conf_intervals[i]) - else: - notch_min, notch_max = computeConfInterval(d, med, iq, - bootstrap) - - # make our notched box vectors - box_x = [box_x_min, box_x_max, box_x_max, cap_x_max, box_x_max, - box_x_max, box_x_min, box_x_min, cap_x_min, box_x_min, - box_x_min] - box_y = [q1, q1, notch_min, med, notch_max, q3, q3, notch_max, - med, notch_min, q1] - # make our median line vectors - med_x = [cap_x_min, cap_x_max] - med_y = [med, med] - # calculate 'regular' plot - else: - # make our box vectors - box_x = [box_x_min, box_x_max, box_x_max, box_x_min, box_x_min] - box_y = [q1, q1, q3, q3, q1] - # make our median line vectors - med_x = [box_x_min, box_x_max] - - def to_vc(xs, ys): - # convert arguments to verts and codes - verts = [] - #codes = [] - for xi, yi in zip(xs, ys): - verts.append((xi, yi)) - verts.append((0, 0)) # ignored - codes = [mpath.Path.MOVETO] + \ - [mpath.Path.LINETO] * (len(verts) - 2) + \ - [mpath.Path.CLOSEPOLY] - return verts, codes - - def patch_list(xs, ys): - verts, codes = to_vc(xs, ys) - path = mpath.Path(verts, codes) - patch = mpatches.PathPatch(path) - self.add_artist(patch) - return [patch] - - # vertical or horizontal plot? - if vert: - - def doplot(*args): - return self.plot(*args) - - def dopatch(xs, ys): - return patch_list(xs, ys) - else: - - def doplot(*args): - shuffled = [] - for i in xrange(0, len(args), 3): - shuffled.extend([args[i + 1], args[i], args[i + 2]]) - return self.plot(*shuffled) + if not self._hold: + self.cla() - def dopatch(xs, ys): - xs, ys = ys, xs # flip X, Y - return patch_list(xs, ys) + holdStatus = self._hold - if patch_artist: - median_color = 'k' + for pos, width, stats in zip(positions, widths, bxpstats): + # outliers coords + flier_x = np.ones(len(stats['outliers'])) * pos + flier_y = stats['outliers'] + + # whisker coords + whisker_x = np.ones(2) * pos + whiskerlo_y = np.array([stats['q1'], stats['whislo']]) + whiskerhi_y = np.array([stats['q3'], stats['whishi']]) + + # cap coords + cap_left = pos - width * 0.25 + cap_right = pos + width * 0.25 + cap_x = np.array([cap_left, cap_right]) + cap_lo = np.ones(2) * stats['whislo'] + cap_hi = np.ones(2) * stats['whishi'] + + # box and median coords + box_left = pos - width * 0.5 + box_right = pos + width * 0.5 + med_y = [stats['med'], stats['med']] + + # notched boxes + if shownotches: + box_x = [box_left, box_right, box_right, cap_right, box_right, + box_right, box_left, box_left, cap_left, box_left, + box_left] + box_y = [stats['q1'], stats['q1'], stats['cilo'], + stats['med'], stats['cihi'], stats['q3'], + stats['q3'], stats['cihi'], stats['med'], + stats['cilo'], stats['q1']] + med_x = cap_x + + # plain boxes else: - median_color = 'r' + box_x = [box_left, box_right, box_right, box_left, box_left] + box_y = [stats['q1'], stats['q1'], stats['q3'], stats['q3'], + stats['q1']] + med_x = [box_left, box_right] - whiskers.extend(doplot(wisk_x, [q1, wisk_lo], 'b--', - wisk_x, [q3, wisk_hi], 'b--')) - caps.extend(doplot(cap_x, [wisk_hi, wisk_hi], 'k-', - cap_x, [wisk_lo, wisk_lo], 'k-')) + # draw the box: if patch_artist: - boxes.extend(dopatch(box_x, box_y)) + boxes.extend(dopatch( + box_x, box_y, **boxprops + )) else: - boxes.extend(doplot(box_x, box_y, 'b-')) - - medians.extend(doplot(med_x, med_y, median_color + '-')) - fliers.extend(doplot(flier_hi_x, flier_hi, sym, - flier_lo_x, flier_lo, sym)) + boxes.extend(doplot( + box_x, box_y, **boxprops + )) + + # draw the whiskers + whiskers.extend(doplot( + whisker_x, whiskerlo_y, **boxprops + )) + whiskers.extend(doplot( + whisker_x, whiskerhi_y, **boxprops + )) + + # maybe draw the caps: + if showcaps: + caps.extend(doplot( + cap_x, cap_lo, **boxprops + )) + caps.extend(doplot( + cap_x, cap_hi, **boxprops + )) + + # draw the medians + medians.extend(doplot( + med_x, med_y, **medianprops + )) + + # maybe draw the means + if showmeans: + if meanline: + means.extend(doplot( + [box_left, box_right], + [stats['mean'], stats['mean']], + **meanprops + )) + else: + means.extend(doplot( + [pos], [stats['mean']], **meanprops + )) + + # draw the outliers + fliers.extend(doplot( + flier_x, flier_y, **flierprops + )) # fix our axes/ticks up a little if vert: diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 570b91846f8c..edbcf29322ca 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1832,6 +1832,145 @@ def delete_masked_points(*args): return margs +def boxplot_stats(X, whis=1.5, bootstrap=None): + ''' + Returns list of dictionaries of staticists to be use to draw a series of + box and whisker plots. See the `Returns` section below to the required + keys of the dictionary. Users can skip this function and pass a user- + defined set of dictionaries to the new `axes.bxp` method instead of + relying on MPL to do the calcs. + + Parameters + ---------- + X : array-like + Data that will be represented in the boxplots. Should have 2 or fewer + dimensions. + + whis : float (default = 1.5) + Determines the reach of the whiskers past the first and third + quartiles (e.g., Q3 + whis*IQR). Beyone the whiskers, data are + considers outliers and are plotted as individual points. Set + this to an unreasonably high value to force the whiskers to + show the min and max data. (IQR = interquartile range, Q3-Q1) + + bootstrap : int or None (default) + Number of times the confidence intervals around the median should + be bootstrapped (percentile method). + + Returns + ------- + bxpstats : A list of dictionaries containing the results for each column + of data. Keys are as + ''' + + def _bootstrap_median(data, N=5000): + # determine 95% confidence intervals of the median + M = len(data) + percentiles = [2.5, 97.5] + + # initialize the array of estimates + estimate = np.empty(N) + for n in range(N): + bsIndex = np.random.random_integers(0, M - 1, M) + bsData = data[bsIndex] + estimate[n] = np.percentile(bsData, 50) + + CI = np.percentile(estimate, percentiles) + return CI + + def _compute_conf_interval(data, med, iqr, bootstrap): + if bootstrap is not None: + # Do a bootstrap estimate of notch locations. + # get conf. intervals around median + CI = _bootstrap_median(data, N=bootstrap) + notch_min = CI[0] + notch_max = CI[1] + else: + # Estimate notch locations using Gaussian-based + # asymptotic approximation. + # + # For discussion: McGill, R., Tukey, J.W., + # and Larsen, W.A. (1978) "Variations of + # Boxplots", The American Statistician, 32:12-16. + N = len(data) + notch_min = med - 1.57 * iqr / np.sqrt(N) + notch_max = med + 1.57 * iqr / np.sqrt(N) + + return notch_min, notch_max + + # output is a list of dicts + bxpstats = [] + + # convert X to a list of lists + if hasattr(X, 'shape'): + # one item + if len(X.shape) == 1: + if hasattr(X[0], 'shape'): + X = list(X) + else: + X = [X, ] + + # several items + elif len(X.shape) == 2: + nrows, ncols = X.shape + if nrows == 1: + X = [X] + elif ncols == 1: + X = [X.ravel()] + else: + X = [X[:, i] for i in xrange(ncols)] + else: + raise ValueError("input `X` must have 2 or fewer dimensions") + + if not hasattr(X[0], '__len__'): + X = [X] + + ncols = len(X) + for ii, x in enumerate(X, start=0): + stats = {} + + # arithmetic mean + stats['mean'] = np.mean(x) + + # medians and quartiles + stats['q1'], stats['med'], stats['q3'] = \ + np.percentile(x, [25, 50, 75]) + + # interquartile range + stats['iqr'] = stats['q3'] - stats['q1'] + + # conf. interval around median + stats['cilo'], stats['cihi'] = _compute_conf_interval( + x, stats['med'], stats['iqr'], bootstrap + ) + + # highest non-outliers + hival = stats['q3'] + whis * stats['iqr'] + wiskhi = np.compress(x <= hival, x) + if len(wiskhi) == 0 or np.max(wiskhi) < stats['q3']: + stats['whishi'] = stats['q3'] + else: + stats['whishi'] = max(wiskhi) + + # get low extreme + loval = stats['q1'] - whis * stats['iqr'] + wisklo = np.compress(x >= loval, x) + if len(wisklo) == 0 or np.min(wisklo) > stats['q1']: + stats['whislo'] = stats['q1'] + else: + stats['whislo'] = min(wisklo) + + # compute a single array of outliers + stats['outliers'] = np.hstack([ + np.compress(x < stats['whislo'], x), + np.compress(x > stats['whishi'], x) + ]) + + bxpstats.append(stats) + + return bxpstats + + # FIXME I don't think this is used anywhere def unmasked_index_ranges(mask, compressed=True): """ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf index 1ef558832b9169958465afbcc03324068a1b046e..6603608c683246796da6e8ad0f3d061b0c176fa0 100644 GIT binary patch delta 1305 zcmZ3b@m^!Y7RGvELxH`&!+&tJTglb`zQMKP$XbDel2f+{sJeQ#{`xLieeCyC-{?si zUDvFQt>0JLoWIAqQ%FI6$u#Xh_TCe&J3Y=&uH~tSy}o)goBOQw`;Y96hDFAaE9u#rB|)y)|=|He0=?B-ih$XU%ltc98xg;b6zND)m`VaYp2Sm z`b#}dPMyoddp+j;gx#0i`zyj_a+}{BI=$m)wES-NoR$_(7Mo4+cfK5^>Dt}FGXHDQ+7n1I;uV) z;mN0c@5C)%C+yjBfVb#mo5CPBC+@G-nMsJf~A6F z8fQm-_-CtJ#On3vptXA?W8q!y6A~re`Ib>}Q&da*T%C?w)wq89>=LCDEag3qE=}#f z+B(6(DZ0_FN9*i`euIOLZr^=({OGgqkMFDheAvI^;Qc8!542sCzL~svX)gZp{oeIH z3hqzunJ^a5i>f82AzQH2HWMH!S8_Pd#UwubI1sfX$eP>4n z0|ot%%7Rn{eb@YAeGX?3Ld|Sx89>Wx4Lx?Q0tBfp74bW{fGBYqo z7c((3v_Ka#G&0Aq!_d?e>;eM=FND_(EzQi(H5(ZkV>sW)1g06G*TmG!Wb$ocS1xly MBQ8}{SARDy01jpb#Q*>R delta 1245 zcmaE_u}WjZ7RLH31_FD3hyUPc-!<=Jc+J`duRBp;1r9O-xs6%dSGcaIpD$VcMrC&F z#@nuzDK?MKRL?uJS@^tvq)OW;D8)tepAh&f+}(O8LrJ{A#b3NB{M-7hn*syjDNIbjfS(-h7kE zjJ35Z6ADa(iwq7v?`cj=lGc6alF8v*8xa00gs0?7$ObdVZ!fIb+A=Es{y+BMPmDrZ z>pEMHN~0Nb;^ujT-4vGH_iEOPwMK5nZ!}b+vgh3YwtlvJuG%f;r=>517H)fbphRd< zCuhU=d*WRo7!^gas0E)`mH(%xtDq-zWVo8R3=PbLv)v1y!4Ti*3HeI3d*lq z*-xD3Gh=G)29t}f(Nn6CSAUn&J{WebS58T_rcAb~{g(leL_-OT@m()YYn9 zG*+=`=Fyco8%*bU`W0;doApTl{Ps;-|LETPQ2yCIthRW{$EO^xGNry;3p^geTXoBp z?P9{)C951a`d?3A)s|Wl;o59kdojUuH(T~PLtpFuCljVRK60M+>~vV*xoi6hdFJ^? z^}R|j-FWHwdk5xMS7UDq6m1i1E}M35bKKn(;UDUw*1!9Dd#ct1CF91)j?I%-al}Z! zZT);rn%h|`V@B+%1`BqX{R)$GCQtU=r}pFWq3{0Oj8!3bbkbu|-}|o8XzsiyHYb1f z+Nr_emkbYW)+r5|c`jy7s!`Vo8zEWm$v@BbiaoXbU-A3P*PPFvAD++u`)vBIMXQC` zt!}iP+A~Y}t62T2JI7zxeX^9YnPfB1!_MHm*S;CTpM-=y+3xDMVR`jQ`R3uwH?L35 zSv2QVZ)IutJ>5+iXFQHq7C0C=FbKc=+?cDDD!#X~(=~Krm*v4p6Bc}Z!K6RIMT_m2 z&wNp3IUUBi&CA-d`WkdY7>~cub5OT(7kbGeP|nabFZi~nT7!^~)&aS@tS@3GDo88O zd0p_O*^qViUxf>G`<8yMEcot!Ovm6~(uC;0tQA+L=T2s16XrHlFi^-(%HpyxG}!!s z^!0NSOA2!GOL8)k^orB; z@={AC#|XLBn;07@7=VC6o&pz`VPIlpX^tUgY-EWkW@>;bW)5^3nt8^S7vxF_)^UtG^o;07R+p6aWAK diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png index 797d133e371e5782b542523fff94dbd14c609715..f17404f5ad484eed914be7dbad3331bab2528d1f 100644 GIT binary patch literal 11065 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 G?f(E}<3XAL literal 11891 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 Ph|c$h=L^pMc @@ -30,42 +30,42 @@ z +M166.86 236.45 +L200.34 236.45 +L200.34 222.672 +L191.97 216 +L200.34 209.328 +L200.34 195.55 +L166.86 195.55 +L166.86 209.328 +L175.23 216 +L166.86 222.672 +L166.86 236.45" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M183.6 236.45 +L183.6 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M183.6 195.55 +L183.6 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L191.97 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M175.23 175.68 +L191.97 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L191.97 216" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> @@ -73,86 +73,78 @@ L191.97 216" style="fill:none;stroke:#ff0000;"/> M-3 0 L3 0 M0 3 -L0 -3" id="me594928b4b" style="stroke:#0000ff;stroke-linecap:butt;stroke-width:0.5;"/> +L0 -3" id="md4acab13e0" style="stroke:#0000ff;stroke-width:0.5;"/> - + + - - - + +L406.8 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L406.8 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M398.43 256.32 +L415.17 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M398.43 175.68 +L415.17 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M398.43 216 +L415.17 216" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> - - - - - - - - - + + - + +L0 -4" id="m93b0483c22" style="stroke:#000000;stroke-width:0.5;"/> - + - + +L0 4" id="m741efc42ff" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -173,20 +165,20 @@ L12.4062 0 z " id="BitstreamVeraSans-Roman-31"/> - + - + - + - + - + @@ -216,7 +208,7 @@ Q49.8594 40.875 45.4062 35.4062 Q44.1875 33.9844 37.6406 27.2188 Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + @@ -224,24 +216,24 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + +L4 0" id="m728421d6d4" style="stroke:#000000;stroke-width:0.5;"/> - + - + +L-4 0" id="mcb0005524f" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -305,7 +297,7 @@ Q6.59375 17.9688 6.59375 36.375 Q6.59375 54.8281 13.0625 64.5156 Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + @@ -313,19 +305,19 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + - + - + @@ -333,19 +325,19 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + - + - + @@ -353,75 +345,75 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -431,22 +423,22 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +L518.4 388.8" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +L72 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png index ea43a079599c3210bae4d3616d56a7e9b1cb5362..cba447aca198b321a617bc92629d3dd83bf21495 100644 GIT binary patch literal 1775 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png index c3136d0e13e709eb49178d0a1c64cb250664fa28..614c60487d300e706ce3fdada6da1163c31f2fa0 100644 GIT binary patch literal 1870 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*+7b=*vT`50|;t3QaXTq&H|6f zVg?2_H4tW;FKnd@WPI>+aSW-L^Y-q>Y>`Zv;}6Z-eRr+p_q2&v+p-{aS9Z>ptyiXW z2~K(=zc7rGF?@@U>?#XlRhAmHN*Dbt(fr=^_(743TKStdd!GEZ5ok%h_UF*- z?~i*@)VMFdEQw-Z`g`R&14FH#gTgchrj0BD9jXluI*cqyoC*To42=;?97lvmkP4MQ zuq@N-vPo?}&;)^)lUa{GQDBy&)<>_n;T!B zulnxIpq-+evhm*ELQWpyd>mTKaC@6>?{e2eTQ8aTivHg9i-D96C7yll-zU-l^M z2+O*8`5fs%NMgW4oZ)-gwI? znJ;F2UDxiNfHT>AYQ9jsZU6pwyV_qRFAL@E>vW={qwQ_dN=vue)cvUlTYQo6b*wq# zoXzLw+fSc6cdmE;?YnpPelc5dF(bt}~Y~t zzvcgbzn|f^Jk>^S`qZgYCH>sQ*OcF7DL8lS+O<=kJ{8@(b4Ta-^XLD6{soe$#l@TN z>?&QoX6@Qi1I5iLdta+H*j9Xb5f~XCuYZ1?t@iTe%fsvLrx-tJ9- zQ`*U@>#Kh;Y|D+Fwd`ee&&=J$&;2C(k3aqT`ufusFJ4@F+~3!CYPNa)soUH0Prtpr z{W;ih{j6Ebx@zBs?cjRz{(nr}@3+T~|M~Y@|LL1I*MIW3EMHUZx@_w0Ten_SxA@ik z{Z;zw$;rttff4YjT5a;lFE1}Iznp)6U#W#;?1p>)*S-H6`&actX-P#z#J#=M;dV7Y z0;Wxyw(ifqrAwE7`uF#@kzD`jhYu6mg^j$g?T%xzNUyH0e){FhmN#$SOgVb==>I?e zfaIpln}gLRd)~Z#`*i}?PreM#-#mG;WKYG%MLIenaGD2LM9A>{mSdi=FzVYDW1^tv<*(a zm!}+Kev}mP{$Ggvfo-|9#Rp7-y}zukmpXIvW@P2pSD}(TY_ literal 2369 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 JcV`3V_+Rb5N9X_m literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png new file mode 100644 index 0000000000000000000000000000000000000000..53bb0c2bd6e645a6b202870b3d5fb86c6fb1eb32 GIT binary patch literal 2159 zcmb_ec~BF17VaD*Ed?Yf15u8I;DUlmHeLv92nH0C1x5iyWD+P;1}K3E3@8Z(MMRN9 z0YN3eFyd5kDmZdO+$=#wAP@z#fN%&&l$*#Aj%-K98m-ztrfT+9~Whc(r=r~?3?>Fnfi3;%-LogJO%!AGzAhcLuKqB>Wl z)dkDjWNiWfptiGvy+?G;M1NDTuVgcC=J3Pqos1&m<0JVRlVi@bimf!g(9PD(%Bwkb z7u|JZ>OBY;Z|jUMQypa4r-Ywt-;sSa;d=F>$JCE-ha#LttqEx78cBU>$J~+& zD*S-twzPO|%}P=d>D!}5weLBV2T(2GV8-7#RPX;iWKuXu%|zWK$B!bwYg_Bor&IF_ zaB(Bs=6qkZw!Xgp6{Dz{(}x^1V*9#Tq2ie5BfGL%d&OeByw_6USZP8RmikorTXVHy z`zo7U_lFJ`tn}_&8faWdGfj6+AIWvb^D3KkXvOnMVS>Z5|t)H1ZrUgFANC(Jr%wl57R|oLzlY!dRLC2 zk~Se)7kLGaluP3=F))v%A!X3Foh>N z_gnbtyHC}z6Bk(v<8{wc^M-z4FgcQ5)|@hZI8^yz;;9**VBUyZz2C$ZDRk~_;xRpP zb;_mV&C=k;YCw&#hBzap>=f|JaoGz3tLSenb6L>4nxv7&5J&`kSr|g2+<|V?( z$@4kTTCQAT8Tt75NY+A_%-v6`s+Mhke~3)Ozom~KjwtOYu=Au%AD2+>x~SSSdfx?* z(9ypnb!92sa<1!-+VMIbQx*`53hasg$6`q61 z$;mpY?Im2Yed=|T&!*{v7fl*Jd3kqJiC8;1+J zEH7t6ZAM00VQIucTN$GI#sOXtjW}eso5_(E1uYI0yn@018IX-|!BaVn4>0*`4#{=% z7$8Dp^DQACfs8=co{_WTGO#D(&vfg$$v{P~IKP|{I6_tYlB$YEi7%zEBvUPE9cQ^6 zV*DhdBm*E@`}ojXA{2GcQug0v8EW$lr_RJHXOY-L1wo6KQXu_crazq4PV-J+b7Y9; zZ)%8~ygL+5aW7hW1e|^GSt%!;Wn#mOmcNmbN>N8j;3qywJAbuctDx%c?k=>eYHW-x z9I>~6Tlu%Nw46}%1(2`HB5H>#<5(JTaTQ&3Sgl@mD_MSNZ?zuoB178Y2)RtuWnOcA z(22%LHI$bib80jb94wyC(#9RS4k%i|oR)pK{2egl%Z=2(5=!K-)4Q^^8*w#D+Wj9p z&BSmi9DwbtwB>!<1Jux~SHj6iRNyu?(D0p>U^|~=#RfDXWHsVoTaMtVGzQL$#^5AC z1RD@;9#1&Hlk5NDm-%y4)X4ky@B8xFBy-s=reAgZ-YrgeDw9)kJ8{%c3!DFo9JU7y zj%4_0eKDIcz%K_PevgVP$Whmg?JJL?(*BqsQ1LsQ;D!0R^L|Oc9WXNbjQ~8uuHG=Y zn>?;+B!#j6w7N!|%HS$6*nHzZ5i@U653vDXL*gN#A~P`r55@x~1r`q%7(CA76G3+` z1yHMpgAi!o$q=8%L4+k_{4o#?e+~1m+2LiK+@ZTY_oU4paowFF@v@5ofW}mpGLCVx zB7Xm>&rq)Ig^d^^+v$Kn+^x!})hDr%j#E96zM|bg;MP?v?y~^?KlAhx{oJcZ8$@k} RROpWZaCUTcC_Biw{2%iozl{I@ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..8c79adacf6c49107760555c944cff3f4accc3c21 GIT binary patch literal 2161 zcmb`JdpOg39LImV#8NRwsno+tH>af21xKdQMRF+0B^l~agv}ZoZ9A0ig*uTk-A;+J zCY{(BWlnKcB&XyIi_O`wskKYkW@g*@p`J(mQBTh~zvuZ~zQ4~O-{1H1e!t$|-|;Q} z-c!tM%m4tG;}cKRlM$kv z0>5r{rD1&)`uAH%;q}RF%A*n8 zp^s5uQD}^--y77hUr+iov4$!r{Z`lWTOkxSzFk5qXS$zkPSO!ZX(d8vC6~+hS{zsr zjjdP@b}_;fk;P^^^fa&W)oLYyVQlylBXdIQ)6^p`r~r$f_*&OhAA2l`xU~V#$?MYy zEG&n6@`s+k)-^+x#GHROkmC{>&<|l0N!=7CZEJZc6-Sqql-nAK^x~~NDp1@Kmv@a* zzpHzH9mSPpXZ<7E(A?BQcd0;59FOv~dEF`;ww^JjGxcGD zb154B>~u=2HH7+bADi)EFx~-uUa=+w8Vx<@TrK~F5v*dGakW+$69R>@`o%eE__XZF z{s+F=DxWu4C&F%Kdv zDj&4o%2f@?qWF|{53pvZa-Co@-oUk1_u3OcIbf-;I_xtR_R8f z&f^l1*slzTH%dyh)4lL4K4@alXB9`GnY!g3n;rY<^gzR)s9)xS&#pFJRb@?OX@R~9 zrnG>@@){9D+4)C-fOwuxY#9;c<~5Gf66=$m0-W>p;79j!%EcOEQ=u7cnn{1 zjYCtfxfgt?rO6cetkZYLvgkqccH{SbzQ{2QmGGwBzjmw@0=6tQLO3yOX@14gT8KMN zX894PPKS1H;qiExxRDO+O&KnIc*YZ$`BEm6A$FkULEVqLr@j|@yqy1u_3#-N1F#Vh z=Q?qkzsnS3$Pm_D_FHgvUrI_!F75dONc6VqP9_NK^mP3gG!r(~&nM|%Yim_)LZkan0LWeAAgG!|*y&!$g0IAv$WM4~-pe(wRZ(+BI z&DK(Nyhv$Q(JGpNXY1wA1Ba%XRWbXS2Nxz|Lnw&NHX=g2R?)~Sun;D@B^rA1<)Lp? zq_TOA#;6%00%cf3qt;1!6@sude-}H9Ys^EQGY1YCvyJI8dK2iE4WW~Y*8`?^!k5N> z7>u|1ePlrZHZoG7Qenvf!KEVXwCn`v;}RSmZ!yReg-W zo&;w)=rQivz3iEnK+_a{LYaGk z+kHP|y{7;G literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png new file mode 100644 index 0000000000000000000000000000000000000000..235d1da545d2875cbb44391ba69ecc3e8ccf85d6 GIT binary patch literal 1888 zcmbtVdo+}39De7b8JaTaqINRFBuo_PBFW5>opEc+UAhdZbYp~-+-4*x)}`EHq_eGr zYT8_;LcY|dTuQZT%3#orOU0O>VG#R~c8@>yoZYkUIp6z!@Av-Bd4J#YJiq66ZpU`| z0+r<|0DuLyjBQQ;AZYOWAa))+2O3o!g%@;?m8}aF9uZjIczCbOVt57tFmKiLgUHh` zCj)>%+HU*SE0g%6-@$tHBnjr%1qx>I^>M*SAYa1P%CCLcFj&#YV)9+s>Mae&UhK~+< zd);(P@DJq+%bHudg#C?CLWwTFLfGFT0VyNu~1$F7^ybM$dKE-DG_;|8VEh3%~EWpvG1j+GF!N~pRcoi+hfVD zii4m^90y@eA=0dwnisW=X>>8lZz2AP??U{Qt1`rT40iuI47O>f0%W6NO)jC(_p`6n z=edXj;+PBZ-BQV@(N^6F0J0NWz#(PF*Dxg=OywAL^EU z;#D7*WzWc1$7T^%ySM{laZgJ>gmVsB5L*LYvSTAoJw zY;=z;S9Wr2_`SfskY&VD0C|dq!DW5L!``hm?&m-h{Z%G#QYJ|W(jIl1UrlCRK5JG+ zBD1Yk6WX;O1ibL*Sbua%H1~43eDbsSdmps3HET)4d;%i2TK6m|-IX~h6$ZFh$L&-m ziSU*eG?DwrA=~vss-KbB0!FTeU0Z5=ND0}JuJKh+SJ3V>{Y&vRiN78zqa zrt?*smd&)lOgzzu-s=&-CM6@^OL+xjAf{pGn_ zZtGYiolXyJ(HhoaqsE8(Ugx&+_ws}XOEwP{a8BVk91n3a*fYQ3__8sP>Mf-kS=3y2Ece);CQS*r}7B}q$%aM<= zW^Dtm=2b!L9Z#jwKuI!G1Cvc4*@sO|gh`U|zY<9Q;3v~{{g*Gy#HgX4_^P3=kiWPn zoaasWjvGi+u!|+zjpOa$2D{W8zr7(t1Z#E3ELQxeYo?lV0bUouSXt0CFAjmZp#qjU zHqjCAI)m|=Il$`fAE`;R^Gp(Dknt+Pmy2M~P3|qx{F=qTxB0;>*A7zxYg_W)N(>9f ztK2Qw6>V$Ah}iF3bStUyNiZcI>Uq)sd$IcfZUO2o^+Umk>6fk&{22{$(}+k^gUwe9 g`yV|8?nIxKqSTP3PE&S^;1&sNZMJW_WyOs76TvbSx&QzG literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png new file mode 100644 index 0000000000000000000000000000000000000000..74ec31b2e25ea48a57d3071894f571ba42f50133 GIT binary patch literal 2159 zcmb7`2~ZPh7RSFFkig=EprhR3Qg(2_Al67Yk^l+>Mo_sF9SB|wghLLwkA%UHVOYiG zN`Vk&ad8$@6qox*xMYykz$gNSTS6F)AP|T_gl%xu+O6HHwX44F_f>aQzkcuc{{Q~* zxQoLMMRi30zz&Mzk&^(x$WZ(mEf1Z;jGJiC1sP#WaYsW(0{Q|A+ACakJRJdm{JyOP zfqMh~9?i_!0QR9kUu#(#yiqI*sj819DtQ9oMwPZ)VdSrr< z?7r!uApdILv3z8!r`y%N>@Zz?fLRbW$H~v$GiBGIrJ?ww`cV-q%k)xh^f|tR^k&57 z5W&*Yk_H$Xz40gzhb8Oe0{8=@GHWlL{51@DPC-Z688A-4(d{aqaVrNi7FZQn-d83K z0haE<_{7_55e%GFOpkb^Nw6xFIt+{BnnLbY_0|Xq`129h>xNh2hJ6}iOlPVOtohd% z`#yX3Rw_96KsB+Ufa4++4{Ia}tYt$)DSK);Hg7&88#6X#*fH>1Gg^Zfom<3dXjQIV z*VN%1e~iKhyU3q(H=i-V@qFy4)Kg3z(8|FS>*Bx7(#4Yb>yHX1Wurqg=j@E%u-?ry?_nMt9(O7@Z88HwSZY@H2AEfJ#NB zo*VyGYbecS;FoW76LpG62XZN~cOtHgwdr$-b0&cYoVCoOG5r>vh5rA*+X`wnNH z-_8~Fl~si{$4b|_514J@vN6=_G{&*?tg+i1@3+D{X+`How9mNr*b;2c&+Mz_SY!IP zy`8w4)n11MUqd4tPrDypu#2B%E?gY+&*N%4U!^g+QTNj(DT_zCF!l@BW+&upMT#%n zaWB0+qMhRmepc``%4>yfv2_vs4~|vxhM4P{*cVM2xLZHsZ z@U*3sm1cN&k6`Jml z{MrltD+i6EOd!}6hS_U%<9~SrN}nqHH7TU_*VvCdmnVS!*(*Pu@kcZ~^uFtqP|=N2 zrv6dr#gIy+&O;U&1BFJTzb`q^t&0>sDzGSutMPLVGJWiXGH00vy zT9Y&yUKg}1OPH0;&(BxgcIAY;YyOj>9aJ^sJ&{BbIqR(I%Z(lOrp;$1CswYkhNwIV ze2nT*W(N;XI!@PYZoWv`80CL=Ej2DqvQqNGn>I?6MM;7+;B6oz+(6Qib>QWk_>OT= zQ1VWeAkH9ieK;(Bs$XqtbGR< z-5QWg(_3l9X^Iha;T{{nJV~TKMWhCl;kU5Nm|ZF87kQz0&vQ74IiZ~iw*E<+j~Y0? zKN&ed!%p`0Uw?W&vsIxbqf5{6ge_2ejA0n3WEjd)sgJz;u(uLGyyF)(GYTyiRCxPu zF?E7_(}LhJa=P^2_jAz`C(YUW%HI#H2bEo_Sk4n7(D+>t{io$Fh39s=3fR(1-uVnwq(C>#w2fkY>c|y;oD@Dfoy)M+!DvA7VyjS3or zQ5ZJa(8K~G)tn5TxgLDlQ2(;p?H2HVi|zm5b^%q(<@!#%803lFWiELnxWpZ*q8*if z4SK!XI5po$>4$CzIn~mx_beYmG6N1G<)?p108b!&@n9pqPS!g#_gio82}=T@PQr)A zuz)Yh+ZYV`hL)C=W+ztH%ElBE$WfH`8Aui(F$DP^f!Nlm2s-}Q6o6Sk9^oki-bhA3 zB^ti<+=uXALgzuGdouj;@4yj}5Ro53gO8)RWi>)v`lJ=lFUCT>$-^9TKLMyQSuMt@ za7zrN+p%<@^~sXbedVGo(39zyVwTOlH^3IIU)ZgngJg%d6H|{WZ>3VdTeb9WtyS9? zWkWz1LpDHTY$y78K38!cl8T`RQ9+R3Jm)t-7BIVFv33Am`ioOcH|b09yr5=&@S(q0 zX~)jW*cYw~{u|rII$Mtdh1obbLh0z|yXdD6{p7tXs4jjHUgFA8h5i@-<*3V%d$!b+ Fe*#OkpHKh* literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png new file mode 100644 index 0000000000000000000000000000000000000000..73520f0118d7b5a6ba015e585d3649be3a926e99 GIT binary patch literal 2096 zcmbVNYgAKL7QP`Da-o3)x{y@S1W=h~G?P`Csvt=~G68KvM;=9SBv>6RAOVBrVFF2Y zcqmJBumXb^S{$gPAu<)D2qZidHGxpH1`-~k4ImGJP#%E*b5c9DotYnP?^^eseb3oz zpL6#9zHev0$7b2uthIq4$Tq;=ZyyAq7{H!FtiU&6YeNheV4iP4FajPvawH4_D-(V6nD8iK+n|?ze|6W1lix+4-{6>vsnN6kZo7ptl zyts7ciOFQDN5P9F^RGiAOh^=FfwVd>NS7gtDj1)&NyylOf)87h@Oyv7QV31hLcJMD zNTu6yax3a(#9%pP>S1}Hu9TIlJzQ1rUBdRFcVndQuGBW9^c|YNlzY1;Rx5d5wplPP zSZlln0!V@)m40@;M)BM>X#%M8p5z ztXrFLP8O`EW1E~wInM^Bi&FP=IFlGGeJ&(QIOq-iKA!rRX@m~IqUrCjVkTaA{sE@b z6;b6e@xOBwc==ZtctUDTzs_f*&7OppnCweJICuZ*x|G9ITbJ~wDAhlUEX z+!#-buhz^hO*%!Cmp6!QgHFKy^?JiweC5%B;pUt0h2;BUq@j5Jol||`7PY$<={X+R z>FSnF?fgJQ?}|aGqMtUP#uF$v0umfdhP^TJmDH3|qv^wr9hMZLVna>dQ=QJ@p0mJ| z5JOB$PVxG)5t`fK)O{`aYU`W1YPx)7E|`4fwSzRF_r}1p?t2=ksku`ju0pSk4c4Sj zAP2R5It6uRGynP(8_f5}a-ptH8T5D3<%<=QGaNaa&DK6YAD9{@(@acFO=%~$<|{u+ zO!OEE8ruUIb;^9{^1?H{Y?#pW2yfExBBY^aWHd(wWkL>2q}J^7ja%c+4a-e(Ytkx- zzcCxf8NKq}=vap2DZ0SZ{nBFHAbIIkrbCTT+}lMC#A4fVH5I$5U!zAO)XN5m;@q9j zT3J!(r69fcUsjMCZ6Slg={GFUiNWAvl{T0hSu?fPuZ;m4I+F~3V&={6uy8!Iw#Ndv>9V)Y;81Ke#p3|ol` zfeKY-m%Xp$5Agiyos2(yvNjEzVBM!?CWI8Aw-6b^s!O;Mfsh*LIP?;HKm^D{#yJw0 zS>qFXy0GezZBu-Z9$lNuyzmI$jeqATn16tYC!T?7*~Xn900Q97hs?+h2?Mx!88T>) zbCvvsMLv$C2m@;3ScSW=Wo$O;_ycJH7#?~PPHhsZItB;TprosL-u=4ysu@IW@d%rHqVc7)Nu}V8AnPQ&Vp9LjnIn^h$U_=pO-h&6hI9yEMLXRarA#F zRcI?YzCt657(LC36iz5kp>SZsvPRGSpgMeml98$=wb&3%9i_68{#D2jQ)g8rGVug zn0vJBu=PU_$`f1*NY$%?gWz`fq5Ed7`?2{3aDj89wLAG6!XI{v;` zcMHJx%O1Z~%O>X#Ypzk!+;D2XvLSi8*?o6QPONP9;nF|oxDK^ieVc|BvVm%&?z;Y_ eZumc^TYT-=iyG_=`C}Al`A`6p?N{l``S{<2HoX zgNO)nln#m<${>ewg+tIm11<_ggn*G_U?O)Qgkw57?vz_QRXa6bRd>Jc@9VE#{rmrZ zuP?efJ7Ux~ssjLk*-PJZ000m)IG%)5;dAJg+5q?hhVS0%4#9&3`CWkT)k5e;!T~_l zSa~3F)_-mY01#>K9y|A_ENLG#K&M-;bx<;Xi56#fGwHdt^Ta+Q1!JaE-C$`HtRwM0C z`)%P70K~3zDiFY|0gq;e0FSGY@cV(-!$_!k9gTDaK$e2|3r2rn%9JP=3KLyv`*Pwu zBG5Ihd#-kDV(rq~C#kZ@!KR9`GRv`0MxjSQaT4cs|IXm{n5UAGLxr-jmxXS8_H%Bv zt~_3k%6W~&V!xoile^q<zS>BY)9w^ryny_VcmbfBhRd~~T{VW+{?CxcXdAla zQ}Cl0-JnB4OTv~~vcI!LK^<;#OBz#agNr~tD>1^2!qc>4?%36Ik^tu3m`2=(kez-6 zFo7<{es^aHey@1`Yy4uUD%5PsRrS}nnkT~k+cC~#U>b35E98?uZO<%(vpE!t`1})j zi{PDb`22!KieMT6YVDH)uTW5@ACW|J{P-nyhugn!f2V3+GxS5bn4?%yAj4-8o3%+( z$$T$`V#1|9nIA*l*zy*g5>+M|nS6GFOf0Ev3;p>jeQfSDWam3-dMJ z6|y#q9Lma1x1O$|hE#eap3KxYn-(Peu9d~{nbv=<+TIP>5+Vm=AruqNN-6Mn2QjDj z)pjv!WrrL5v`Vw*W>j{w37^QRT9lN!S7}zklGC$4bGf|KGP%)r0H#C< z7sAy@R)hP84(xdECv>d5u`NAkMKROPCZaMUqM~||?>^{kI$r7@6BEPW1X*X6FE20a z6FnKo$JAqWfzoat>MAFJ;Dl+Y-zAf?`jzHjSw%o3w{yHpU<%iJrzG=@i0Ez1W7#dx z*AWFTZ1P@=U^YBLkt3Y{Iv3sKAVYlgF)Xxq@gbvA*D-;@um@&S56y`*UfyM&HT@!y zN$7YWGyG0Xep?Pg7RA`XjQZdjCO;_|Nkn&{4K3j3Ab&Zv%e;(D$`!j@*Z;x=#IEUlhM_6jIlL%HmZ3E*GMd8Nxz0K>Ph8(V5<&7I6PYYpnjImWf@f_dPWk|&zn=+_ohZ_bQ!@7;n z`|7l&Q?ifFqc#_^fbCmRRl*ei^>@%0&#mT9@4i=E-J6_j&NvU z+f5(b6&Mth2Dt{m-%el6j;b*XwE3tZ$(KLcH6v24PnPsFry)YRp#h@;{o5g50*S>e zp!FE|1RINKNW9f(K!+Wp52oa@fGyu7ngrXEm)*5I(r`>-v_8EL?_`ar*|ZI`22<&P z1p)ckjbnI!{~+_4X%R9VM-sUHi0UvUI~|NG$X8ZI0!*YLq@~TJNAp2XW6I1m#r#OJ z^*$gYtF~6$+t9$jp&LwDtu43ipDHn28edPldf~++H%XcAq%4czdL)j`8&Y{JwK3#z z85CY-4{Fp28Y{Smy9W!?kbw`;Q2aOjG)mR8#^%PmD;Mh?9e*y9$+}kRq*AGuF76CL zpcbQ$NsH@(DLIvA&z{9lH{Nc=YhCbIL3fl3irP9>PlaqDqXiu~nWjYAgc@>tc(;&s zbS*kC(Na9;08W-H`++S40*$I2TiBzj@_#e;cWhiw91sefFM6F+$vil;?I@f@bz_6o zuNdstz`}qb!Kgt`>pULh(|iv9QSE5fbg*IVc9#~2R)V5sRZmp zO|ASV<#1d0ZI4fo{QJ$Y`>=Vs(YwR8*6p_Y$7U$7jS63yE?PkOxA#T0)&K2vb>zgW zOB*a+Yi~4uyHv|LQ$NyPTLzC3K9HLVcK}`<^e3C_uZgapW*Dh;TYt7Tf`1@@y$;TM JN_I29`31=~tabnZ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png new file mode 100644 index 0000000000000000000000000000000000000000..c95f103856ff11a852d1517d8930abb487cda942 GIT binary patch literal 5119 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 A9RL6T literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 7043ccb670f7..9e680e767784 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1037,6 +1037,132 @@ def bump(a): plt.subplot(2, 2, 4) plt.stackplot(list(xrange(100)), d.T, baseline='weighted_wiggle') +@image_comparison(baseline_images=['bxp_baseline'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_baseline(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats) + +@image_comparison(baseline_images=['bxp_horizontal'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_baseline(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_xscale('log') + ax.bxp(logstats, vert=False) + +@image_comparison(baseline_images=['bxp_customoutlier'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_customoutlier(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + flierprops = dict(linestyle='none', marker='d', markerfacecolor='g') + ax.bxp(logstats, flierprops=flierprops) + +@image_comparison(baseline_images=['bxp_withnotch'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_shownotches(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, shownotches=True) + +@image_comparison(baseline_images=['bxp_nocaps'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_nocaps(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, showcaps=False) + +@image_comparison(baseline_images=['bxp_withmean_point'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_showmean(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, showmeans=True, meanline=False) + +@image_comparison(baseline_images=['bxp_withmean_custompoint'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_showcustommean(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + meanprops = dict(linestyle='none', marker='d', markerfacecolor='green') + ax.bxp(logstats, showmeans=True, meanprops=meanprops) + +@image_comparison(baseline_images=['bxp_withmean_line'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_showmeanasline(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, showmeans=True, meanline=True) + +def test_bxp_bad_widths(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + assert_raises(ValueError, ax.bxp, logstats, widths=[1]) + +def test_bxp_bad_positions(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + assert_raises(ValueError, ax.bxp, logstats, positions=[2,3]) + @image_comparison(baseline_images=['boxplot']) def test_boxplot(): diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 1aba8e02eacb..624a00cc943a 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -6,8 +6,9 @@ from datetime import datetime import numpy as np -from numpy.testing.utils import assert_array_equal -from nose.tools import assert_equal, raises +from numpy.testing.utils import assert_array_equal, assert_approx_equal, \ + assert_array_almost_equal +from nose.tools import assert_equal, raises, assert_true, assert_list_equal import matplotlib.cbook as cbook import matplotlib.colors as mcolors @@ -92,3 +93,94 @@ def test_allequal(): assert(cbook.allequal([])) assert(cbook.allequal(('a', 'a'))) assert(not cbook.allequal(('a', 'b'))) + + +class Test_boxplot_stats: + def setup(self): + np.random.seed(937) + self.nrows = 37 + self.ncols = 4 + self.data = x = np.random.lognormal(size=(self.nrows, self.ncols), + mean=1.5, sigma=1.75) + self.known_keys = sorted([ + 'mean', 'med', 'q1', 'q3', 'iqr', + 'cilo', 'cihi', 'whislo', 'whishi', + 'outliers' + ]) + self.std_results = cbook.boxplot_stats(self.data) + + self.known_nonbootstrapped_res = { + 'cihi': 6.8161283264444847, + 'cilo': -0.1489815330368689, + 'iqr': 13.492709959447094, + 'mean': 13.00447442387868, + 'med': 3.3335733967038079, + 'outliers': np.array([ + 92.55467075, 87.03819018, 42.23204914, 39.29390996 + ]), + 'q1': 1.3597529879465153, + 'q3': 14.85246294739361, + 'whishi': 27.899688243699629, + 'whislo': 0.042143774965502923 + } + + self.known_bootstrapped_ci = { + 'cihi': 8.939577523357828, + 'cilo': 1.8692703958676578, + } + + self.known_whis3_res = { + 'whishi': 42.232049135969874, + 'whislo': 0.042143774965502923, + 'outliers': np.array([92.55467075, 87.03819018]), + } + + def test_form_main_list(self): + assert_true(isinstance(self.std_results, list)) + + def test_form_each_dict(self): + for res in self.std_results: + assert_true(isinstance(res, dict)) + + def test_form_dict_keys(self): + for res in self.std_results: + keys = sorted(list(res.keys())) + assert_list_equal(keys, self.known_keys) + #for key in keys: + # assert_true(key in self.known_keys) + + def test_results_baseline(self): + res = self.std_results[0] + for key in list(self.known_nonbootstrapped_res.keys()): + if key != 'outliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_nonbootstrapped_res[key] + ) + + def test_results_bootstrapped(self): + results = cbook.boxplot_stats(self.data, bootstrap=10000) + res = results[0] + for key in list(self.known_bootstrapped_ci.keys()): + assert_approx_equal( + res[key], + self.known_bootstrapped_ci[key] + ) + + def test_results_whiskers(self): + results = cbook.boxplot_stats(self.data, whis=3) + res = results[0] + for key in list(self.known_whis3_res.keys()): + if key != 'outliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_whis3_res[key] + ) From b429dcf0056d7167bef448b13e60394db33a4aa4 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 14:04:24 -0800 Subject: [PATCH 02/43] BUG: fixed cbook tests for Python 2.6 --- lib/matplotlib/tests/test_cbook.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 624a00cc943a..efd53c03075e 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -6,9 +6,9 @@ from datetime import datetime import numpy as np -from numpy.testing.utils import assert_array_equal, assert_approx_equal, \ - assert_array_almost_equal -from nose.tools import assert_equal, raises, assert_true, assert_list_equal +from numpy.testing.utils import (assert_array_equal, assert_approx_equal, + assert_array_almost_equal) +from nose.tools import assert_equal, raises, assert_true import matplotlib.cbook as cbook import matplotlib.colors as mcolors @@ -100,7 +100,7 @@ def setup(self): np.random.seed(937) self.nrows = 37 self.ncols = 4 - self.data = x = np.random.lognormal(size=(self.nrows, self.ncols), + self.data = np.random.lognormal(size=(self.nrows, self.ncols), mean=1.5, sigma=1.75) self.known_keys = sorted([ 'mean', 'med', 'q1', 'q3', 'iqr', @@ -145,9 +145,8 @@ def test_form_each_dict(self): def test_form_dict_keys(self): for res in self.std_results: keys = sorted(list(res.keys())) - assert_list_equal(keys, self.known_keys) - #for key in keys: - # assert_true(key in self.known_keys) + for key in keys: + assert_true(key in self.known_keys) def test_results_baseline(self): res = self.std_results[0] From 04937b53259303dbcfa777ac0a450f11f30fefec Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 18:47:26 -0800 Subject: [PATCH 03/43] ENH: allow for custom patch artist and labels to the boxplots --- lib/matplotlib/axes/_axes.py | 48 ++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 3e3f4f34265e..e605ecf17293 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2934,9 +2934,33 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, # lists of artists to be output whiskers, caps, boxes, medians, means, fliers = [], [], [], [], [], [] + # empty list of xticklabels + datalabels = [] + + # translates between line2D and patch linestyles + linestyle_map = { + 'solid': '-', + 'dashed': '--', + 'dashdot': '-.', + 'dotted': ':' + } + # plotting properties if boxprops is None: - boxprops = dict(linestyle='-', color='black') + if patch_artist: + boxprops = dict(linestyle='solid', edgecolor='black', + facecolor='white') + else: + boxprops = dict(linestyle='-', color='black') + + if patch_artist: + otherprops = dict( + linestyle=linestyle_map[boxprops['linestyle']], + color=boxprops['edgecolor'] + ) + else: + otherprops = dict(linestyle=boxprops['linestyle'], + color=boxprops['color']) if flierprops is None: flierprops = dict(linestyle='none', marker='+', @@ -3011,6 +3035,9 @@ def dopatch(xs, ys, **kwargs): holdStatus = self._hold for pos, width, stats in zip(positions, widths, bxpstats): + # try to find a new label + datalabels.append(stats.get('label', pos)) + # outliers coords flier_x = np.ones(len(stats['outliers'])) * pos flier_y = stats['outliers'] @@ -3062,19 +3089,19 @@ def dopatch(xs, ys, **kwargs): # draw the whiskers whiskers.extend(doplot( - whisker_x, whiskerlo_y, **boxprops + whisker_x, whiskerlo_y, **otherprops )) whiskers.extend(doplot( - whisker_x, whiskerhi_y, **boxprops + whisker_x, whiskerhi_y, **otherprops )) # maybe draw the caps: if showcaps: caps.extend(doplot( - cap_x, cap_lo, **boxprops + cap_x, cap_lo, **otherprops )) caps.extend(doplot( - cap_x, cap_hi, **boxprops + cap_x, cap_hi, **otherprops )) # draw the medians @@ -3102,19 +3129,24 @@ def dopatch(xs, ys, **kwargs): # fix our axes/ticks up a little if vert: - setticks, setlim = self.set_xticks, self.set_xlim + setticks = self.set_xticks + setlim = self.set_xlim + setlabels = self.set_xticklabels else: - setticks, setlim = self.set_yticks, self.set_ylim + setticks = self.set_yticks + setlim = self.set_ylim + setlabels = self.set_yticklabels newlimits = min(positions) - 0.5, max(positions) + 0.5 setlim(newlimits) setticks(positions) + setlabels(datalabels) # reset hold status self.hold(holdStatus) return dict(whiskers=whiskers, caps=caps, boxes=boxes, - medians=medians, fliers=fliers) + medians=medians, fliers=fliers, means=means) @docstring.dedent_interpd def scatter(self, x, y, s=20, c='b', marker='o', cmap=None, norm=None, From 88435bc309fa7a9242e3aeb31545821bc5615be8 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 16:51:17 -0800 Subject: [PATCH 04/43] WIP: more tests and add option to toggle box TST: added tests for all of bxp and more boxplot tests TST: fixed boxplot baseline image TST: added baseline images for bxp --- lib/matplotlib/axes/_axes.py | 25 +-- .../test_axes/boxplot_with_CIarray.png | Bin 1870 -> 2492 bytes .../test_axes/bxp_custombox.png | Bin 0 -> 3212 bytes .../test_axes/bxp_custommedian.png | Bin 0 -> 2032 bytes .../test_axes/bxp_custompatchartist.png | Bin 0 -> 3964 bytes .../test_axes/bxp_custompositions.png | Bin 0 -> 2044 bytes .../test_axes/bxp_customwidths.png | Bin 0 -> 2238 bytes .../baseline_images/test_axes/bxp_nobox.png | Bin 0 -> 1859 bytes .../test_axes/bxp_patchartist.png | Bin 0 -> 1919 bytes .../test_axes/bxp_scalarwidth.png | Bin 0 -> 2049 bytes .../test_axes/bxp_with_xlabels.png | Bin 0 -> 3477 bytes .../test_axes/bxp_with_ylabels.png | Bin 0 -> 3252 bytes lib/matplotlib/tests/test_axes.py | 183 ++++++++++++++++-- 13 files changed, 182 insertions(+), 26 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custommedian.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_customwidths.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_patchartist.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index e605ecf17293..102df2806c56 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2928,7 +2928,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, def bxp(self, bxpstats, positions=None, widths=None, vert=True, patch_artist=False, shownotches=False, showmeans=False, - showcaps=True, boxprops=None, flierprops=None, + showcaps=True, showbox=True, boxprops=None, flierprops=None, medianprops=None, meanprops=None, meanline=False): # lists of artists to be output @@ -3026,7 +3026,9 @@ def dopatch(xs, ys, **kwargs): if widths is None: distance = max(positions) - min(positions) widths = [min(0.15 * max(distance, 1.0), 0.5)] * N - elif len(widths) != len(bxpstats): + elif np.isscalar(widths): + widths = [widths] * N + elif len(widths) != N: raise ValueError(datashape_message.format("widths")) if not self._hold: @@ -3077,15 +3079,16 @@ def dopatch(xs, ys, **kwargs): stats['q1']] med_x = [box_left, box_right] - # draw the box: - if patch_artist: - boxes.extend(dopatch( - box_x, box_y, **boxprops - )) - else: - boxes.extend(doplot( - box_x, box_y, **boxprops - )) + # maybe draw the box: + if showbox: + if patch_artist: + boxes.extend(dopatch( + box_x, box_y, **boxprops + )) + else: + boxes.extend(doplot( + box_x, box_y, **boxprops + )) # draw the whiskers whiskers.extend(doplot( diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png index 614c60487d300e706ce3fdada6da1163c31f2fa0..9fb197604e80b9a7c61dab53444d72dc89ee8f8c 100644 GIT binary patch literal 2492 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png new file mode 100644 index 0000000000000000000000000000000000000000..6329a2c51a712e2d69c961cbf4d85e284cd23398 GIT binary patch literal 3212 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

0RN5-Ph;C=6?U&8QW<_+R+(AKHIapHw{N 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png new file mode 100644 index 0000000000000000000000000000000000000000..35a0de4fa8ee2ff45e8f713857777304c480cbc5 GIT binary patch literal 2044 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png new file mode 100644 index 0000000000000000000000000000000000000000..b12088d71ee7206d3db94fec22ba8c1cfb8746db GIT binary patch literal 1859 zcmb7_c~BEq9LHadk_90NpmGTbh(-}wlzM==ET9xn(Lsf%bqFF81q#^6Q6#c3Xi%u6 zqFf>ouhfD{MTnv#Q9wka!2_&-92J8~(|}YElZ7s^YMu5U-^}j5o%h?Bx4-Y_`+fV? z1b$;;WNichF!A?W76bqR!RG^z0e;`%QX7Ro$UJZVbr3#MpsktsyJ4K)1|9$gGbb*B z$lPN(K6&Q9%xhgzvHHo<7@H8wz4!X%lbH8!0Gs@CSNDKUueN9}V9^r0xDkhd5z=_^1Qn+VmLAD?Y@?-}CDs*}<9qp}r{%+hi!{3GVHks> z1;p}Ge4r*}xId;qYy93TU>TEHbn6KxQ{o)1dvzPr*eLE)nsU+lE48*yN4h-6YHH8I z*44en!Xz!me>1FY3lG~w^t;--*jv=M^Z`6l2w;1R;agNM>x-~@Di*=gu^xe9oT4VN zceqse?=aK7?YER+3FFEAseQKRzKCJSjr#lthoT5ueY^MzZ%Dd2Wq}W2bjl;*k;%&3 zMb8GYksMiPfFKJ0xeD)BCzFo%Qci?qoHr+=c;C+lL59r1;=w*M#PWLnty6J&qO6ER z7Mf1JPLt!=YDdYBUt-s&Hcw2xbosK?`B2&Jzm&sT>{3n5#-Zi_LuP97NKCdURXUDk zo31pumVW??Ta?}3-*1w0nnrNkPahD1S7HG-#jOOpCMFAIbogLB#Wg-~)@xp!&mdvt zQS$(-V?fCN05gu|YVq2vO) zy3z@dbedw*<_!WqswUk`W%&+7s7Xot_~_k?VY=3UkA3=JdYHGwu8c-l zV2`G#5^k@`)x)SJJ>STw-tt^_mimY&p)sQynmt!*LGnzfyzHeyQdcEclc4fBqlyq9 zlNZw!tALC-%9`#-|BMX54gO+hq+XM}i0y;5F$@H)T5=vnWtHvfjU65~mx{madNIsJ zON=PId1QlGIF}RCm#)TmsSl?h_y2q^rt(nrLhrg6wmFR~Mp7z|=SCIOok*?j(loZT zv=j+^({Pz7H)rf)GgN?JR7E@HjufmFxXXtLWcG^p@{(scqR50{AtLX>ze51L_cWd@ zPOge3Cw=x@G&1I0Ff#sVWPOyjc}{ME6@)p}M z+O|?ERLn%f4N+c(O|f~Gu;qQ6HO3gTchbvg|7y?v&bcqoz31Hf{GRXU`95d%Iy{HrK+SQ-HWvT@>B#*gq=KBoO$8yy1KUuRJ7xaJp+KdR(vq)^(To(h3&#^|crJ^K{JjJ!m>` z|J`woB)gD1cPhGj^mqgNnB_^;S6H)?0WXjA{>|i>(w-N17IuX4d|2gmTxWCt3SU;| zcUGZwg6oHJ$_|V3V}TT!aa-n$jb%iacZ`1IX@f9{6`g%8U4zka8T)&FSeqY}El7mn zdsq%gF(K0J7=$zfGTmWN>7Y8UHb@RRKr_c>RA%7GD9QJ9!E&C$E`Dz zMWnh74g&#)1m>74#9%i4wkVkG!ST*%u$jv*QuU>>i&Xj>+~T0aT7=plEkfR^FA9oe z*$!4+GV#TDQQ5#Q1f^I&@4XHTRcK zISk29iNMit^CHJ^{JbnOgiGy?6UXSZ8RPsugu3wEVoqW4gk!Mn1ueV5zU3gijHw2!nHn@!Sy+i1ykkaqnw7;QIQHSFprQ;*>>G z8lCd6WJD*z3V9hRyehDvfQ&VxVGcsrl+C+a9ea7b6MGhVhz|qvt8n~SfK%r)tzGMS z)!R`5EJsZXayzp3n%FVCk~bUZU!6BhwxSc#tl$g+9uylBVP+G!R{cx@x4sd)$FG^J z4pe}bPyRx7lSVL8g9N)Eo##$vYuhlKv~2(d$Y)99wF>S&^eDpf2bd6^AE;sj@2O(n z^J0z5=pPkVmRfL|%6jExJ++qW!5~W=o%oXOaq-L2t-Iq(+S)pM>=NYCB_yn&#XRk| zSilbxMF~c`dGu3Y5%}hq5bEo!$4u(AgA~n^F^gah_c=lQl+P9YU=Lcws+SK_`scW^ zxLz|eGwnZDoce9F&sHnyXa9Y$n&wzaH8IJJLBW@wuEZ82j zyrE!~#bQlx$HdYtyITeErQM;OgM)(?%eP|`&XUT+7gHKR)G?$-hJXMkts$TRgJT$f zb{$2_f3gh3S1>Jx(79)Z5Zz(XZ4p3!P)^+Ft*r7FVsdv-)t7Fgl#Z?b!h8q)e||%O ze~kKexxk&TUK9&>Cni7EB%sO>XM00I-0HJkKPg0&>!Bl_VD{FKJYxX^&IO?ijYFN& zD<%dkLo3i4NB-u{*Fm-qX1Xkf4|V=+O>p3BDqjBLCd2hnnYA8r6Fot<&h8w2vi^}5TIZ+bSYM;hz3L@fzYm~ zgNv)+qva9b7BYfLMFfF}D9-@J0*-`Kph>7{NO==NNDnxx)1CdJJF{oz-t#+;IrrS( z@B91wPJ{&eEwy;p0sycyz~3hn0E7kY-%&I8-v2>O0_<>n?|^U=HUS!U9FEPC{6FOb zV76}ILBuQE900&y3Gnd>KUARY_e_Y1BsC7?c0JA??()XIBo3cxPP^glmOA|W9)x_jXj^Y-OLe;%?8n47uV#Vp&>CV}Ig3ZMP&cmpN5SsAi0% z9hsZiA5+$w>p3!V@97o4Q|mH*D_Yq3iKM%vb!a4y@M2{1ocg3tx95`%WtGuJ*$3|oPAN<1kheDQiy=G)3;z2QbZvCzyLmm62_87Tz zU&eTl>Sr%KnA&3d?CKWtbp5rvJ2Pr(7?Y-P3pM6Efk&tg813JS#N{xJKioaVtg0#e zUI5Yx-uf!kgEN@dcz)-+L0cTY#kLa8`WL;;$rHp*tQwH`0D}LK6)Y!YO_9U=b_^Oc z6Y6TcY*9pP-u!^FRpVJ_tk8|~Jk)Bn_H$hr`Q!W`cLt+IpMHOPMR|GoD$d>mn5KW8 zcZo5KHumY%s~-MDlnaAk{BQPZf;+CeAZyMMDI)nZ&%u1dkK`ZLiR4tl8Y?NInD1R& zW{*qayetTLsWCIW_Hl$BB;M;=PA*(Z>Y!V$RrD&cDE-`+M4J8ha)aM4Y-5;`@!33j ztC2e(8hrbkq5h~Yjv zUP8yw$gCIXG#3|L%ATR>!I_LmrYK8F+ScuGDLhL`r}}xzxlEA?45Twn#_67eqw_OE zJ++3Bsdio6K5bbYjK*a5WMslkqkdO{%MSVc9NLB)jQ7vGwg-JO)wfH0_uz0-s!7ND z*-OHs@u+C(b=yo3AqEJNV zSgT$M3ud(wnUzg}Gd5mdePfgF9Ep416OYnuAP`^% zad}Xi66q_aA<0gpwy>kz7h?#{E*k>k@-%Yhi*o(t{#_bX{P^S1BFnWXWkWD0BT+Ni zYtpMDCE}O_lTn>!HU9`ljHi7xr@JBmTC4}t4;LO1OXlgp0YMJ>B3ch}?h`ss6DPtJ zCv%!l6XVJW|B0U;CDP&@b?L_Tg+M^rFbiG#7c3AUv2vmv#si;>084f!YD%n9o$6eo4h~n zc*qTo1Zr<3&#tjMF^9NvgC8-^+Lne{SN zF%*^nEX|B9vdl!>U{Kh+3yy5wB2-8e=wDYU3}vkILW&Fy4yp{Y_@@z)ZrOCJ@=jr4 z;pCw`fma1UhHSLiuW5aQwBmXabSJ#P!dkt@w^-aFX&_LnaH{6j=py?5ZN6eW%#G+J zS%>dfSa$yHjd}V{Y-Wj22h@bM|k?(I2ZVA9U;>yz{bHrzmT8;*QxV@{IH^`}z;8=WX}^ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png new file mode 100644 index 0000000000000000000000000000000000000000..fda6da5059fde07d871b6483136ad9afc415831f GIT binary patch literal 3477 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png new file mode 100644 index 0000000000000000000000000000000000000000..07867fa6ddbc9f5a3e38de678f6d0aada8c5b68f GIT binary patch literal 3252 zcmcgvdpwi-AAdG0l3OVp&8D2_r^{4~TqCzt$em)S(*?QB+(z3{NQzF5OYXPm5KD%z zAsuIxBd2m2tuf@%7RzOAneltn>G$e)&Og6@&hL4>-p}{@Jg@H`&-e3rU%pT3F-JRj zS#?=h)6yy;e^(n{3O};g3_FIQqdE^+0DyzK3zQKd!!?#BXum#OmS$?#2 zoYB#;pvF8E6&0V1ZJ4}Uw<64-1SQ^6)DTT>;+Irq<0$S==&+%?L5Duu?{^w_V3|Ub zHT4! zW07PWcc9MhO(IszD03CA*fIhqpJfeSFkTZ{u=VG-gahUC+T=%%9;qV`(PnEoZ-QVK zMlqC)lf6CuKnY7m6KIg(s3#{U`(%(2zqHP+hxi;SJr@3FQD91B?{6_gQ&NA2-n;il z5&DuQ<>Yg=`^fKN8O@E(wH_wa(}tc&m*fkeQhI3s^q<4}axLfb^73jXFEs=uGxv1d zQKyN1W#qWVeS@Q= zpV>jZDDJg@=_Q>xa|SE~I=tf(qXyW6LhY10BoQ$E>g*u?WJ8M7wK98YMTDDqv1hHv z%VmkUF@%L?o`PtoChzPx+rT3Y_4hyJo5ZvZl2CKnWaNc~9~F@1cQs6+eInyG*~mUR z=A9-~AYj$7@;9i_mn$nPkR;mTWW~0D_sf|o=$ZblVNekRNt#Mh#fF>N>cT8J(`}m* zHN(xi zCG090%L}o5F)R*_TA@GA2NdMwrZSz(pLca7qC;L+8Uor$G$;Ym}gwM~-wa3R>-tep+kkoWNTnoKo?OyYT z!uiHFkUbjz2lkYg+uGT=Q<(Y7OPY^&=9sV_rq*%!e9xcVxKG;Sbf#y2{(YS@+7SHCDN^yW8fEAcNlC-WJ5| z)BE@Dud^fTc&&do3Tn-NB0cmX0v1 zUaz|60YcjT7m?^GO7tZEEcvEB8eN&G;}pbd3h@k0E3*$!)6MTSE|5FN7Cq26h!XT^ zY0aFObx=#Lv%B%u8&E`;-l-}g92N`tOwj4TuDvBxRKhGf+hkuU!7lPPyy{OC_fB@8 zmV0Y}^y}9HaX6P4b`[Tu(U40=gR_R!4f)V>ux{3tPaNsKlh8yg&MOo%%guiQB@ z5+JXr*oKg@-!w9xZ)gF7kVCb|z;D~g>(tcMab{4{hsW(zp}AzMQji?6iH$e*eCr;o z^Hm3-2cA4xcDqk8k#@Cv#&@$q(}sh1x5nckV>PXj?nY!+|9>V0zb0!$0G=9x$GZs# zlH_J(Sll?SN;&sV)Yo*87s_b9_MkA`_vD1dnsk@B?crnm%c4K};_v&3F8=M~I_2GT zH#4;~le`SyaU6i0oE!rS3qIi*>DK3Lak&zesW1flAetR}-58w1+Y6|TIsiI4I)bJ7 zK3-KZ5oadLu~x&1*B4IO0Fx6Fc!oyIx#9K;#?kKw43J>Qx7I++LJbJRq>i~7rj}ZA zscJ?rYG@tq{lMPdXV18xJ23|v&Nfo16(>SxPwKA$d%fmMuS316W`?mwXq8y!Tl{vh zV=-yD<@>;d1Rp=*r`tuu#XyXUcBV5J-sls1a*n*6#?tSoE(c8YOIX}`Z{N}DN7y6x z`9?Vz?`*NKa0B8^{eAmJ!#8$nIOX|YRUMXs0jgi(W9=6M*mROzAq9DJ@UsDY-uGXd zk6n!qeK0;AM<@OnAO5NkYgY9iL5U|K!QK!2`ZtMvmD)EWO~GF+`D)o?ftuOFoF1i0 z4aVLB=BDj*1%E?oTgiz}t=G+CYBQQ~VnK6DY)SN@S5rKkDfZgWsAVzcY(GwNy%t-w z4Mzp>7aIoemhIrC_Mo65LuMZ!9yLlYbOeC(TnjKLRKcL|;6h_3e*2ETvMjD+*f;u- zB<@*uY+wZ2*9)~w5EdQ=OI zb~nRf`9TfS4Rrd+XN9gmySArm%84>UmiEwxix2$$)ur1h Date: Mon, 2 Dec 2013 19:17:32 -0800 Subject: [PATCH 05/43] DOC/REF: added docstring for axes.bxp and refactored all references to outliers as fliers --- lib/matplotlib/axes/_axes.py | 132 ++++++++++++++++++++++++++++++++--- lib/matplotlib/cbook.py | 2 +- 2 files changed, 123 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 102df2806c56..4067d1a43633 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2879,6 +2879,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, - caps: the horizontal lines at the ends of the whiskers. - fliers: points representing data that extend beyone the whiskers (outliers). + - means: points or lines representing the means. **Example:** @@ -2928,11 +2929,121 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, def bxp(self, bxpstats, positions=None, widths=None, vert=True, patch_artist=False, shownotches=False, showmeans=False, - showcaps=True, showbox=True, boxprops=None, flierprops=None, - medianprops=None, meanprops=None, meanline=False): + showcaps=True, showbox=True, showfliers=True, + boxprops=None, flierprops=None, medianprops=None, + meanprops=None, meanline=False): + """ + Drawing function for box and whisker plots. + + Call signature:: + + bxp(self, bxpstats, positions=None, widths=None, vert=True, + patch_artist=False, shownotches=False, showmeans=False, + showcaps=True, showbox=True, boxprops=None, flierprops=None, + medianprops=None, meanprops=None, meanline=False): + + Make a box and whisker plot for each column of *x* or each + vector in sequence *x*. The box extends from the lower to + upper quartile values of the data, with a line at the median. + The whiskers extend from the box to show the range of the + data. Flier points are those past the end of the whiskers. + + Function Arguments: + + *bxpstats* : + A list of dictionaries containing stats for each boxplot. + Required keys are: + 'med' - The median (scalar float). + 'q1' - The first quartile (25th percentile) (scalar float). + 'q3' - The first quartile (50th percentile) (scalar float). + 'whislo' - Lower bound of the lower whisker (scalar float). + 'whishi' - Upper bound of the upper whisker (scalar float). + Optional keys are + 'mean' - The mean (scalar float). Needed if showmeans=True. + 'fliers' - Data beyond the whiskers (sequence of floats). + Needed if showfliers=True. + 'cilo' & 'ciho' - Lower and upper confidence intervals about + the median. Needed if shownotches=True. + 'label' - Name of the dataset (string). If available, this + will be used a tick label for the boxplot + + *positions* : [ default 1,2,...,n ] + Sets the horizontal positions of the boxes. The ticks and limits + are automatically set to match the positions. + + *widths* : [ default 0.5 ] + Either a scalar or a vector and sets the width of each box. The + default is 0.5, or ``0.15*(distance between extreme positions)`` + if that is smaller. + + *vert* : [ False | True (default) ] + If True (default), makes the boxes vertical. + If False, makes horizontal boxes. + + *patch_artist* : [ False (default) | True ] + If False produces boxes with the Line2D artist + If True produces boxes with the Patch artist1 + + *shownotches* : [ False (default) | True ] + If False (default), produces a rectangular box plot. + If True, will produce a notched box plot + + *showmeans* : [ False (default) | True ] + If True, will toggle one the rendering of the means + + *showcaps* : [ False | True (default) ] + If True, will toggle one the rendering of the caps + + *showbox* : [ False | True (default) ] + If True, will toggle one the rendering of box + + *showfliers* : [ False | True (default) ] + If True, will toggle one the rendering of the fliers + + *boxprops* : [ dict | None (default) ] + If provided, will set the plotting style of the boxes + + *flierprops* : [ dict | None (default) ] + If provided, will set the plotting style of the fliers + + *medianprops* : [ dict | None (default) ] + If provided, will set the plotting style of the medians + + *meanprops* : [ dict | None (default) ] + If provided, will set the plotting style of the means + + *meanline* : [ False (default) | True ] + If True (and *showmeans* is True), will try to render the mean + as a line spanning the full width of the box according to + *meanprops*. Not recommended if *shownotches* is also True. + Otherwise, means will be shown as points. + + Returns a dictionary mapping each component of the boxplot + to a list of the :class:`matplotlib.lines.Line2D` + instances created. That dictionary has the following keys + (assuming vertical boxplots): + - boxes: the main body of the boxplot showing the quartiles + and the median's confidence intervals if enabled. + - medians: horizonal lines at the median of each box. + - whiskers: the vertical lines extending to the most extreme, + n-outlier data points. + - caps: the horizontal lines at the ends of the whiskers. + - fliers: points representing data that extend beyone the + whiskers (fliers). + - means: points or lines representing the means. + + **Example:** + + .. plot:: pyplots/boxplot_demo.py + """ # lists of artists to be output - whiskers, caps, boxes, medians, means, fliers = [], [], [], [], [], [] + whiskers = [] + caps = [] + boxes = [] + medians = [] + means = [] + fliers = [] # empty list of xticklabels datalabels = [] @@ -3040,9 +3151,9 @@ def dopatch(xs, ys, **kwargs): # try to find a new label datalabels.append(stats.get('label', pos)) - # outliers coords - flier_x = np.ones(len(stats['outliers'])) * pos - flier_y = stats['outliers'] + # fliers coords + flier_x = np.ones(len(stats['fliers'])) * pos + flier_y = stats['fliers'] # whisker coords whisker_x = np.ones(2) * pos @@ -3125,10 +3236,11 @@ def dopatch(xs, ys, **kwargs): [pos], [stats['mean']], **meanprops )) - # draw the outliers - fliers.extend(doplot( - flier_x, flier_y, **flierprops - )) + # maybe draw the fliers + if showfliers: + fliers.extend(doplot( + flier_x, flier_y, **flierprops + )) # fix our axes/ticks up a little if vert: diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index edbcf29322ca..1cf82deeabca 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1961,7 +1961,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap): stats['whislo'] = min(wisklo) # compute a single array of outliers - stats['outliers'] = np.hstack([ + stats['fliers'] = np.hstack([ np.compress(x < stats['whislo'], x), np.compress(x > stats['whishi'], x) ]) From 614736d6689e77993f030b95be9303ab90de01de Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 19:19:44 -0800 Subject: [PATCH 06/43] REF/TST: update boxplot_stats test with outliers changed to fliers ENH/TST: boxplots stats now takes a labels kwarg MNT: minor PEP8 whitespace fix --- lib/matplotlib/cbook.py | 22 ++++++++++++++++--- lib/matplotlib/tests/test_cbook.py | 34 ++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 1cf82deeabca..b31c712f392a 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1832,7 +1832,7 @@ def delete_masked_points(*args): return margs -def boxplot_stats(X, whis=1.5, bootstrap=None): +def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): ''' Returns list of dictionaries of staticists to be use to draw a series of box and whisker plots. See the `Returns` section below to the required @@ -1848,7 +1848,7 @@ def boxplot_stats(X, whis=1.5, bootstrap=None): whis : float (default = 1.5) Determines the reach of the whiskers past the first and third - quartiles (e.g., Q3 + whis*IQR). Beyone the whiskers, data are + quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are considers outliers and are plotted as individual points. Set this to an unreasonably high value to force the whiskers to show the min and max data. (IQR = interquartile range, Q3-Q1) @@ -1857,6 +1857,10 @@ def boxplot_stats(X, whis=1.5, bootstrap=None): Number of times the confidence intervals around the median should be bootstrapped (percentile method). + labels : sequence + Labels for each dataset. Length must be compatible with dimensions + of `X` + Returns ------- bxpstats : A list of dictionaries containing the results for each column @@ -1926,9 +1930,21 @@ def _compute_conf_interval(data, med, iqr, bootstrap): X = [X] ncols = len(X) - for ii, x in enumerate(X, start=0): + if labels is None: + labels = [None] * ncols + elif len(labels) != ncols: + raise ValueError("Dimensions of labels and X must be compatible") + + for ii, (x, label) in enumerate(zip(X, labels), start=0): + # empty dict stats = {} + # set the label + if label is not None: + stats['label'] = label + else: + stats['label'] = ii + # arithmetic mean stats['mean'] = np.mean(x) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index efd53c03075e..a00e8b876c8d 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -105,7 +105,7 @@ def setup(self): self.known_keys = sorted([ 'mean', 'med', 'q1', 'q3', 'iqr', 'cilo', 'cihi', 'whislo', 'whishi', - 'outliers' + 'fliers', 'label' ]) self.std_results = cbook.boxplot_stats(self.data) @@ -115,13 +115,14 @@ def setup(self): 'iqr': 13.492709959447094, 'mean': 13.00447442387868, 'med': 3.3335733967038079, - 'outliers': np.array([ + 'fliers': np.array([ 92.55467075, 87.03819018, 42.23204914, 39.29390996 ]), 'q1': 1.3597529879465153, 'q3': 14.85246294739361, 'whishi': 27.899688243699629, - 'whislo': 0.042143774965502923 + 'whislo': 0.042143774965502923, + 'label': 0 } self.known_bootstrapped_ci = { @@ -132,7 +133,11 @@ def setup(self): self.known_whis3_res = { 'whishi': 42.232049135969874, 'whislo': 0.042143774965502923, - 'outliers': np.array([92.55467075, 87.03819018]), + 'fliers': np.array([92.55467075, 87.03819018]), + } + + self.known_res_with_labels = { + 'label': 'Test1' } def test_form_main_list(self): @@ -151,7 +156,7 @@ def test_form_dict_keys(self): def test_results_baseline(self): res = self.std_results[0] for key in list(self.known_nonbootstrapped_res.keys()): - if key != 'outliers': + if key != 'fliers': assert_statement = assert_approx_equal else: assert_statement = assert_array_almost_equal @@ -174,7 +179,7 @@ def test_results_whiskers(self): results = cbook.boxplot_stats(self.data, whis=3) res = results[0] for key in list(self.known_whis3_res.keys()): - if key != 'outliers': + if key != 'fliers': assert_statement = assert_approx_equal else: assert_statement = assert_array_almost_equal @@ -183,3 +188,20 @@ def test_results_whiskers(self): res[key], self.known_whis3_res[key] ) + + def test_results_withlabels(self): + labels = ['Test1', 2, 3, 4] + results = cbook.boxplot_stats(self.data, labels=labels) + res = results[0] + for key in list(self.known_res_with_labels.keys()): + assert_equal(res[key], self.known_res_with_labels[key]) + + @raises(ValueError) + def test_label_error(self): + labels = [1, 2] + results = cbook.boxplot_stats(self.data, labels=labels) + + @raises(ValueError) + def test_bad_dims(self): + data = np.random.normal(size=(34, 34, 34)) + results = cbook.boxplot_stats(data) From ec12ef9bd7e2143116ebe3f499d322bb25d00735 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 06:58:31 -0800 Subject: [PATCH 07/43] MNT: reran boilerplate.py --- lib/matplotlib/pyplot.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index a67fc8580b1e..ed036c956623 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2588,7 +2588,7 @@ def broken_barh(xranges, yrange, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.boxplot) -def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, +def boxplot(x, notch=False, sym=u'b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, conf_intervals=None, hold=None): ax = gca() @@ -2613,7 +2613,7 @@ def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, @_autogen_docstring(Axes.cohere) def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.window_hanning, noverlap=0, pad_to=None, - sides='default', scale_by_freq=None, hold=None, **kwargs): + sides=u'default', scale_by_freq=None, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2710,7 +2710,7 @@ def csd(x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.errorbar) -def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None, +def errorbar(x, y, yerr=None, xerr=None, fmt=u'-', ecolor=None, elinewidth=None, capsize=3, barsabove=False, lolims=False, uplims=False, xlolims=False, xuplims=False, errorevery=1, capthick=None, hold=None, **kwargs): @@ -2735,8 +2735,8 @@ def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.eventplot) -def eventplot(positions, orientation='horizontal', lineoffsets=1, - linelengths=1, linewidths=None, colors=None, linestyles='solid', +def eventplot(positions, orientation=u'horizontal', lineoffsets=1, + linelengths=1, linewidths=None, colors=None, linestyles=u'solid', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -2813,9 +2813,9 @@ def fill_betweenx(y, x1, x2=0, where=None, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hexbin) -def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', - yscale='linear', extent=None, cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, edgecolors='none', +def hexbin(x, y, C=None, gridsize=100, bins=None, xscale=u'linear', + yscale=u'linear', extent=None, cmap=None, norm=None, vmin=None, + vmax=None, alpha=None, linewidths=None, edgecolors=u'none', reduce_C_function=np.mean, mincnt=None, marginals=False, hold=None, **kwargs): ax = gca() @@ -2841,7 +2841,7 @@ def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', # changes will be lost @_autogen_docstring(Axes.hist) def hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, - bottom=None, histtype='bar', align='mid', orientation='vertical', + bottom=None, histtype=u'bar', align=u'mid', orientation=u'vertical', rwidth=None, log=False, color=None, label=None, stacked=False, hold=None, **kwargs): ax = gca() @@ -2885,7 +2885,7 @@ def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hlines) -def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', +def hlines(y, xmin, xmax, colors=u'k', linestyles=u'solid', label=u'', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3066,7 +3066,7 @@ def plot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.plot_date) -def plot_date(x, y, fmt='bo', tz=None, xdate=True, ydate=False, hold=None, +def plot_date(x, y, fmt=u'bo', tz=None, xdate=True, ydate=False, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3145,7 +3145,7 @@ def quiverkey(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.scatter) -def scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, +def scatter(x, y, s=20, c=u'b', marker=u'o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None, **kwargs): ax = gca() @@ -3283,7 +3283,7 @@ def step(x, y, *args, **kwargs): # changes will be lost @_autogen_docstring(Axes.streamplot) def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, - norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, + norm=None, arrowsize=1, arrowstyle=u'-|>', minlength=0.1, transform=None, zorder=1, hold=None): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3378,7 +3378,7 @@ def triplot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.vlines) -def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', +def vlines(x, ymin, ymax, colors=u'k', linestyles=u'solid', label=u'', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3444,7 +3444,7 @@ def cla(): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.grid) -def grid(b=None, which='major', axis='both', **kwargs): +def grid(b=None, which=u'major', axis=u'both', **kwargs): ret = gca().grid(b=b, which=which, axis=axis, **kwargs) draw_if_interactive() return ret @@ -3492,7 +3492,7 @@ def ticklabel_format(**kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.locator_params) -def locator_params(axis='both', tight=None, **kwargs): +def locator_params(axis=u'both', tight=None, **kwargs): ret = gca().locator_params(axis=axis, tight=tight, **kwargs) draw_if_interactive() return ret @@ -3500,7 +3500,7 @@ def locator_params(axis='both', tight=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.tick_params) -def tick_params(axis='both', **kwargs): +def tick_params(axis=u'both', **kwargs): ret = gca().tick_params(axis=axis, **kwargs) draw_if_interactive() return ret @@ -3516,7 +3516,7 @@ def margins(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.autoscale) -def autoscale(enable=True, axis='both', tight=None): +def autoscale(enable=True, axis=u'both', tight=None): ret = gca().autoscale(enable=enable, axis=axis, tight=tight) draw_if_interactive() return ret From ffdbd7d00bca93f24327ba99c8892082f91673d6 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 07:02:54 -0800 Subject: [PATCH 08/43] DOC: added blurb in changelog --- CHANGELOG | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 509eb2aefe21..1e9f48c4ae57 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +2013-12-03 Added a pure boxplot-drawing method that allow a more complete + customization of boxplots. It takes a list of dicts contains stats. + Also created a function (`cbook.boxplot_stats`) that generates the + stats needed. + 2013-10-27 Added get_rlabel_position and set_rlabel_position methods to PolarAxes to control angular position of radial tick labels. From 987893c1e8439c1eed2b58514c7ab6985ed68939 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 07:27:06 -0800 Subject: [PATCH 09/43] MNT: got rid of build-breaking `u''` strings in pyplot --- lib/matplotlib/pyplot.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ed036c956623..828b03da6b1f 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -132,7 +132,6 @@ def switch_backend(newbackend): def show(*args, **kw): """ Display a figure. - When running in ipython with its pylab mode, display all figures and return to the ipython prompt. @@ -2588,7 +2587,7 @@ def broken_barh(xranges, yrange, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.boxplot) -def boxplot(x, notch=False, sym=u'b+', vert=True, whis=1.5, positions=None, +def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, conf_intervals=None, hold=None): ax = gca() @@ -2613,7 +2612,7 @@ def boxplot(x, notch=False, sym=u'b+', vert=True, whis=1.5, positions=None, @_autogen_docstring(Axes.cohere) def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.window_hanning, noverlap=0, pad_to=None, - sides=u'default', scale_by_freq=None, hold=None, **kwargs): + sides='default', scale_by_freq=None, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2710,7 +2709,7 @@ def csd(x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.errorbar) -def errorbar(x, y, yerr=None, xerr=None, fmt=u'-', ecolor=None, elinewidth=None, +def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None, capsize=3, barsabove=False, lolims=False, uplims=False, xlolims=False, xuplims=False, errorevery=1, capthick=None, hold=None, **kwargs): @@ -2735,8 +2734,8 @@ def errorbar(x, y, yerr=None, xerr=None, fmt=u'-', ecolor=None, elinewidth=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.eventplot) -def eventplot(positions, orientation=u'horizontal', lineoffsets=1, - linelengths=1, linewidths=None, colors=None, linestyles=u'solid', +def eventplot(positions, orientation='horizontal', lineoffsets=1, + linelengths=1, linewidths=None, colors=None, linestyles='solid', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -2813,9 +2812,9 @@ def fill_betweenx(y, x1, x2=0, where=None, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hexbin) -def hexbin(x, y, C=None, gridsize=100, bins=None, xscale=u'linear', - yscale=u'linear', extent=None, cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, edgecolors=u'none', +def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', + yscale='linear', extent=None, cmap=None, norm=None, vmin=None, + vmax=None, alpha=None, linewidths=None, edgecolors='none', reduce_C_function=np.mean, mincnt=None, marginals=False, hold=None, **kwargs): ax = gca() @@ -2841,7 +2840,7 @@ def hexbin(x, y, C=None, gridsize=100, bins=None, xscale=u'linear', # changes will be lost @_autogen_docstring(Axes.hist) def hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, - bottom=None, histtype=u'bar', align=u'mid', orientation=u'vertical', + bottom=None, histtype='bar', align='mid', orientation='vertical', rwidth=None, log=False, color=None, label=None, stacked=False, hold=None, **kwargs): ax = gca() @@ -2885,7 +2884,7 @@ def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hlines) -def hlines(y, xmin, xmax, colors=u'k', linestyles=u'solid', label=u'', +def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3066,7 +3065,7 @@ def plot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.plot_date) -def plot_date(x, y, fmt=u'bo', tz=None, xdate=True, ydate=False, hold=None, +def plot_date(x, y, fmt='bo', tz=None, xdate=True, ydate=False, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3145,7 +3144,7 @@ def quiverkey(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.scatter) -def scatter(x, y, s=20, c=u'b', marker=u'o', cmap=None, norm=None, vmin=None, +def scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None, **kwargs): ax = gca() @@ -3283,7 +3282,7 @@ def step(x, y, *args, **kwargs): # changes will be lost @_autogen_docstring(Axes.streamplot) def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, - norm=None, arrowsize=1, arrowstyle=u'-|>', minlength=0.1, + norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, transform=None, zorder=1, hold=None): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3378,7 +3377,7 @@ def triplot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.vlines) -def vlines(x, ymin, ymax, colors=u'k', linestyles=u'solid', label=u'', +def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3444,7 +3443,7 @@ def cla(): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.grid) -def grid(b=None, which=u'major', axis=u'both', **kwargs): +def grid(b=None, which='major', axis='both', **kwargs): ret = gca().grid(b=b, which=which, axis=axis, **kwargs) draw_if_interactive() return ret @@ -3492,7 +3491,7 @@ def ticklabel_format(**kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.locator_params) -def locator_params(axis=u'both', tight=None, **kwargs): +def locator_params(axis='both', tight=None, **kwargs): ret = gca().locator_params(axis=axis, tight=tight, **kwargs) draw_if_interactive() return ret @@ -3500,7 +3499,7 @@ def locator_params(axis=u'both', tight=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.tick_params) -def tick_params(axis=u'both', **kwargs): +def tick_params(axis='both', **kwargs): ret = gca().tick_params(axis=axis, **kwargs) draw_if_interactive() return ret @@ -3516,7 +3515,7 @@ def margins(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.autoscale) -def autoscale(enable=True, axis=u'both', tight=None): +def autoscale(enable=True, axis='both', tight=None): ret = gca().autoscale(enable=enable, axis=axis, tight=tight) draw_if_interactive() return ret From 2562b68004ead06c33bff2c07bac87bc32dd132d Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 07:31:55 -0800 Subject: [PATCH 10/43] MNT: fixed changelog whitespace --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1e9f48c4ae57..504452d34aba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,7 @@ 2013-12-03 Added a pure boxplot-drawing method that allow a more complete customization of boxplots. It takes a list of dicts contains stats. - Also created a function (`cbook.boxplot_stats`) that generates the - stats needed. + Also created a function (`cbook.boxplot_stats`) that generates the + stats needed. 2013-10-27 Added get_rlabel_position and set_rlabel_position methods to PolarAxes to control angular position of radial tick labels. From 7f937f9ce75e8d0c8f4406b3e51ba6b0f9d44829 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 13:21:59 -0800 Subject: [PATCH 11/43] ENH: cbook.boxplot_stats can now set the whiskers to -range- (min/max) or specified percentiles. If IQR is zero, min/max are used --- lib/matplotlib/cbook.py | 39 ++++++++++++++++++++------- lib/matplotlib/tests/test_cbook.py | 43 ++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index b31c712f392a..a39223eede27 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1846,12 +1846,17 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): Data that will be represented in the boxplots. Should have 2 or fewer dimensions. - whis : float (default = 1.5) - Determines the reach of the whiskers past the first and third - quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are - considers outliers and are plotted as individual points. Set - this to an unreasonably high value to force the whiskers to - show the min and max data. (IQR = interquartile range, Q3-Q1) + whis : float, string, or sequence (default = 1.5) + As a float, determines the reach of the whiskers past the first and + third quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are + considers outliers and are plotted as individual points. Set this + to an unreasonably high value to force the whiskers to show the min + and max data. (IQR = interquartile range, Q3-Q1). Alternatively, set + this to an ascending sequence of percentile (e.g., [5, 95]) to set + the whiskers at specific percentiles of the data. Finally, can be the + string 'range' to force the whiskers to the min and max of the data. + In the edge case that the 25th and 75th percentiles are equivalent, + `whis` will be automatically set to 'range' bootstrap : int or None (default) Number of times the confidence intervals around the median should @@ -1954,14 +1959,31 @@ def _compute_conf_interval(data, med, iqr, bootstrap): # interquartile range stats['iqr'] = stats['q3'] - stats['q1'] + if stats['iqr'] == 0: + whis = 'range' # conf. interval around median stats['cilo'], stats['cihi'] = _compute_conf_interval( x, stats['med'], stats['iqr'], bootstrap ) - # highest non-outliers - hival = stats['q3'] + whis * stats['iqr'] + # lowest/highest non-outliers + if np.isscalar(whis): + if np.isreal(whis): + loval = stats['q1'] - whis * stats['iqr'] + hival = stats['q3'] + whis * stats['iqr'] + elif whis in ['range', 'limit', 'limits', 'min/max']: + loval = np.min(x) + hival = np.max(x) + else: + whismsg = 'whis must be a float, valid string, or '\ + 'list of percentiles' + raise ValueError(whismsg) + else: + loval = np.percentile(x, whis[0]) + hival = np.percentile(x, whis[1]) + + # get high extreme wiskhi = np.compress(x <= hival, x) if len(wiskhi) == 0 or np.max(wiskhi) < stats['q3']: stats['whishi'] = stats['q3'] @@ -1969,7 +1991,6 @@ def _compute_conf_interval(data, med, iqr, bootstrap): stats['whishi'] = max(wiskhi) # get low extreme - loval = stats['q1'] - whis * stats['iqr'] wisklo = np.compress(x >= loval, x) if len(wisklo) == 0 or np.min(wisklo) > stats['q1']: stats['whislo'] = stats['q1'] diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index a00e8b876c8d..994e780911b4 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -101,7 +101,7 @@ def setup(self): self.nrows = 37 self.ncols = 4 self.data = np.random.lognormal(size=(self.nrows, self.ncols), - mean=1.5, sigma=1.75) + mean=1.5, sigma=1.75) self.known_keys = sorted([ 'mean', 'med', 'q1', 'q3', 'iqr', 'cilo', 'cihi', 'whislo', 'whishi', @@ -140,6 +140,17 @@ def setup(self): 'label': 'Test1' } + self.known_res_percentiles = { + 'whislo': 0.1933685896907924, + 'whishi': 42.232049135969874 + } + + self.known_res_range = { + 'whislo': 0.042143774965502923, + 'whishi': 92.554670752188699 + + } + def test_form_main_list(self): assert_true(isinstance(self.std_results, list)) @@ -175,7 +186,7 @@ def test_results_bootstrapped(self): self.known_bootstrapped_ci[key] ) - def test_results_whiskers(self): + def test_results_whiskers_float(self): results = cbook.boxplot_stats(self.data, whis=3) res = results[0] for key in list(self.known_whis3_res.keys()): @@ -189,6 +200,34 @@ def test_results_whiskers(self): self.known_whis3_res[key] ) + def test_results_whiskers_range(self): + results = cbook.boxplot_stats(self.data, whis='range') + res = results[0] + for key in list(self.known_res_range.keys()): + if key != 'fliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_res_range[key] + ) + + def test_results_whiskers_percentiles(self): + results = cbook.boxplot_stats(self.data, whis=[5, 95]) + res = results[0] + for key in list(self.known_res_percentiles.keys()): + if key != 'fliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_res_percentiles[key] + ) + def test_results_withlabels(self): labels = ['Test1', 2, 3, 4] results = cbook.boxplot_stats(self.data, labels=labels) From fc804587e99e04bc0a158168fbbc2e16f75efe28 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 13:48:32 -0800 Subject: [PATCH 12/43] TST: added tests and baseline images for new whisker behavior --- lib/matplotlib/cbook.py | 4 +- .../test_axes/boxplot_autorange_whiskers.pdf | Bin 0 -> 4786 bytes .../test_axes/boxplot_autorange_whiskers.png | Bin 0 -> 6749 bytes .../test_axes/boxplot_autorange_whiskers.svg | 380 ++++++++++++++++++ .../test_axes/bxp_precentilewhis.png | Bin 0 -> 3469 bytes .../test_axes/bxp_rangewhis.png | Bin 0 -> 3150 bytes lib/matplotlib/tests/test_axes.py | 38 +- 7 files changed, 419 insertions(+), 3 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index a39223eede27..7b7f43168f9b 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1988,14 +1988,14 @@ def _compute_conf_interval(data, med, iqr, bootstrap): if len(wiskhi) == 0 or np.max(wiskhi) < stats['q3']: stats['whishi'] = stats['q3'] else: - stats['whishi'] = max(wiskhi) + stats['whishi'] = np.max(wiskhi) # get low extreme wisklo = np.compress(x >= loval, x) if len(wisklo) == 0 or np.min(wisklo) > stats['q1']: stats['whislo'] = stats['q1'] else: - stats['whislo'] = min(wisklo) + stats['whislo'] = np.min(wisklo) # compute a single array of outliers stats['fliers'] = np.hstack([ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dfdb5b6f3f9c2ca96467fc467e231512d74cc14e GIT binary patch literal 4786 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 YO&^-}({x3l%mZLzV6I=Rd;Zpc0G9?={r~^~ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg new file mode 100644 index 000000000000..0d070e52b984 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png new file mode 100644 index 0000000000000000000000000000000000000000..dd6b368e71962f00bfbc46e8e72e68b70979a423 GIT binary patch literal 3469 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png new file mode 100644 index 0000000000000000000000000000000000000000..8df2d30167c8454583f24da67040081f1661fedf GIT binary patch literal 3150 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 G6aNJV$alp6 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ca91a76c1ad2..c1cb94244cf9 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1048,6 +1048,34 @@ def test_bxp_baseline(): ax.set_yscale('log') ax.bxp(logstats) +@image_comparison(baseline_images=['bxp_rangewhis'], + extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_rangewhis(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)), + whis='range' + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats) + +@image_comparison(baseline_images=['bxp_precentilewhis'], + extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_precentilewhis(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)), + whis=[5, 95] + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats) + @image_comparison(baseline_images=['bxp_with_xlabels'], extensions=['png'], savefig_kwarg={'dpi': 40}) @@ -1305,10 +1333,18 @@ def test_boxplot(): x = np.hstack([-25, x, 25]) fig, ax = plt.subplots() - # show 1 boxplot with mpl medians/conf. interfals, 1 with manual values ax.boxplot([x, x], bootstrap=10000, notch=1) ax.set_ylim((-30, 30)) +@image_comparison(baseline_images=['boxplot_autorange_whiskers']) +def test_boxplot_autorange_whiskers(): + x = np.ones(140) + x = np.hstack([0, x, 2]) + fig, ax = plt.subplots() + + ax.boxplot([x, x], bootstrap=10000, notch=1) + ax.set_ylim((-5, 5)) + @image_comparison(baseline_images=['boxplot_with_CIarray'], remove_text=True, extensions=['png'], savefig_kwarg={'dpi': 40}) From 85d91dfdabb1bb1d4d770e91e9f1fa7844c7b18e Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 17:57:34 -0800 Subject: [PATCH 13/43] ENH: added remaining bxp kwargs to boxplot --- lib/matplotlib/axes/_axes.py | 83 ++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4067d1a43633..adf7ee137cec 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2790,7 +2790,10 @@ def xywhere(xs, ys, mask): def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, - bootstrap=None, usermedians=None, conf_intervals=None): + bootstrap=None, usermedians=None, conf_intervals=None, + meanline=False, showmeans=False, showcaps=True, + showbox=True, showfliers=True, boxprops=None, labels=None, + flierprops=None, medianprops=None, meanprops=None): """ Make a box and whisker plot. @@ -2823,10 +2826,18 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, If True (default), makes the boxes vertical. If False, makes horizontal boxes. - *whis* : [ default 1.5 ] - Defines the length of the whiskers as a function of the inner - quartile range. They extend to the most extreme data point - within ( ``whis*(75%-25%)`` ) data range. + *whis* : [ float | string | or sequence (default = 1.5) ] + As a float, determines the reach of the whiskers past the first + and third quartiles (e.g., Q3 + whis*IQR, IQR = interquartile + range, Q3-Q1). Beyond the whiskers, data are considered outliers + and are plotted as individual points. Set this to an unreasonably + high value to force the whiskers to show the min and max values. + Alternatively, set this to an ascending sequence of percentile + (e.g., [5, 95]) to set the whiskers at specific percentiles of + the data. Finally, can *whis* be the string 'range' to force the + whiskers to the min and max of the data. In the edge case that + the 25th and 75th percentiles are equivalent, *whis* will be + automatically set to 'range'. *bootstrap* : [ *None* (default) | integer ] Specifies whether to bootstrap the confidence intervals @@ -2862,10 +2873,44 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, default is 0.5, or ``0.15*(distance between extreme positions)`` if that is smaller. + *labels* : [ sequence | None (default) ] + Labels for each dataset. Length must be compatible with + dimensions of *x* + *patch_artist* : [ False (default) | True ] If False produces boxes with the Line2D artist If True produces boxes with the Patch artist + *showmeans* : [ False (default) | True ] + If True, will toggle one the rendering of the means + + *showcaps* : [ False | True (default) ] + If True, will toggle one the rendering of the caps + + *showbox* : [ False | True (default) ] + If True, will toggle one the rendering of box + + *showfliers* : [ False | True (default) ] + If True, will toggle one the rendering of the fliers + + *boxprops* : [ dict | None (default) ] + If provided, will set the plotting style of the boxes + + *flierprops* : [ dict | None (default) ] + If provided, will set the plotting style of the fliers + + *medianprops* : [ dict | None (default) ] + If provided, will set the plotting style of the medians + + *meanprops* : [ dict | None (default) ] + If provided, will set the plotting style of the means + + *meanline* : [ False (default) | True ] + If True (and *showmeans* is True), will try to render the mean + as a line spanning the full width of the box according to + *meanprops*. Not recommended if *shownotches* is also True. + Otherwise, means will be shown as points. + Returns a dictionary mapping each component of the boxplot to a list of the :class:`matplotlib.lines.Line2D` instances created. That dictionary has the following keys @@ -2885,12 +2930,11 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, .. plot:: pyplots/boxplot_demo.py """ - bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap) - if sym == 'b+': + bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap, + labels=labels) + if sym == 'b+' and flierprops is None: flierprops = dict(linestyle='none', marker='+', markeredgecolor='blue') - else: - flierprops = sym # replace medians if necessary: if usermedians is not None: @@ -2921,10 +2965,11 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, artists = self.bxp(bxpstats, positions=positions, widths=widths, vert=vert, patch_artist=patch_artist, - shownotches=notch, showmeans=False, - showcaps=True, boxprops=None, - flierprops=flierprops, medianprops=None, - meanprops=None, meanline=False) + shownotches=notch, showmeans=showmeans, + showcaps=showcaps, showbox=showbox, + boxprops=boxprops, flierprops=flierprops, + medianprops=medianprops, meanprops=meanprops, + meanline=meanline, showfliers=showfliers) return artists def bxp(self, bxpstats, positions=None, widths=None, vert=True, @@ -3060,18 +3105,20 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, if boxprops is None: if patch_artist: boxprops = dict(linestyle='solid', edgecolor='black', - facecolor='white') + facecolor='white', linewidth=1) else: - boxprops = dict(linestyle='-', color='black') + boxprops = dict(linestyle='-', color='black', linewidth=1) if patch_artist: otherprops = dict( - linestyle=linestyle_map[boxprops['linestyle']], - color=boxprops['edgecolor'] + linestyle=linestyle_map[boxprops['linestyle']], + color=boxprops['edgecolor'], + linewidth=boxprops.get('linewidth', 1) ) else: otherprops = dict(linestyle=boxprops['linestyle'], - color=boxprops['color']) + color=boxprops['color'], + linewidth=boxprops.get('linewidth', 1)) if flierprops is None: flierprops = dict(linestyle='none', marker='+', From d079ad2488d2d50d8434b6b37007f52e8cbbe9dd Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 17:58:11 -0800 Subject: [PATCH 14/43] DOC: expanded boxplot_stats docstring --- lib/matplotlib/cbook.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 7b7f43168f9b..f0f13bba1d0a 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1848,12 +1848,12 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): whis : float, string, or sequence (default = 1.5) As a float, determines the reach of the whiskers past the first and - third quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are - considers outliers and are plotted as individual points. Set this - to an unreasonably high value to force the whiskers to show the min - and max data. (IQR = interquartile range, Q3-Q1). Alternatively, set - this to an ascending sequence of percentile (e.g., [5, 95]) to set - the whiskers at specific percentiles of the data. Finally, can be the + third quartiles (e.g., Q3 + whis*IQR, QR = interquartile range, Q3-Q1). + Beyond the whiskers, data are considered outliers and are plotted as + individual points. Set this to an unreasonably high value to force the + whiskers to show the min and max data. Alternatively, set this to an + ascending sequence of percentile (e.g., [5, 95]) to set the whiskers + at specific percentiles of the data. Finally, can `whis` be the string 'range' to force the whiskers to the min and max of the data. In the edge case that the 25th and 75th percentiles are equivalent, `whis` will be automatically set to 'range' From 5a9f610b2638b4c106c1015b971bb196db8a7116 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 23:45:40 -0800 Subject: [PATCH 15/43] DOC: added examples --- examples/statistics/boxplot_demo.py | 75 ++++++++++++++++++++++++++ examples/statistics/bxp_demo.py | 81 +++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 examples/statistics/boxplot_demo.py create mode 100644 examples/statistics/bxp_demo.py diff --git a/examples/statistics/boxplot_demo.py b/examples/statistics/boxplot_demo.py new file mode 100644 index 000000000000..7bc72e30cdf0 --- /dev/null +++ b/examples/statistics/boxplot_demo.py @@ -0,0 +1,75 @@ +""" +Demo of the new boxplot functionality +""" + +import numpy as np +import matplotlib.pyplot as plt + +# fake data +np.random.seed(937) +data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75) +labels = list('ABCD') +fs = 10 # fontsize + +# demonstrate how to toggle the display of different elements: +fig, axes = plt.subplots(nrows=2, ncols=3) +axes[0, 0].boxplot(data, labels=labels) +axes[0, 0].set_title('Default', fontsize=fs) + +axes[0, 1].boxplot(data, labels=labels, showmeans=True) +axes[0, 1].set_title('showmeans=True', fontsize=fs) + +axes[0, 2].boxplot(data, labels=labels, showmeans=True, meanline=True) +axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) + +axes[1, 0].boxplot(data, labels=labels, showbox=False, showcaps=False) +axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) + +axes[1, 1].boxplot(data, labels=labels, notch=True, bootstrap=10000) +axes[1, 1].set_title('notch=True, bootstrap=10000', fontsize=fs) + +axes[1, 2].boxplot(data, labels=labels, showfliers=False) +axes[1, 2].set_title('showfliers=False', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +plt.show() + + +# demonstrate how to customize the display different elements: +boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod') +flierprops = dict(marker='o', markerfacecolor='green', markersize=12, + linestyle='none') +medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick') +meanpointprops = dict(marker='D', markeredgecolor='black', + markerfacecolor='firebrick') +meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') + +fig, axes = plt.subplots(nrows=2, ncols=3) +axes[0, 0].boxplot(data, boxprops=boxprops) +axes[0, 0].set_title('Custom boxprops', fontsize=fs) + +axes[0, 1].boxplot(data, flierprops=flierprops, medianprops=medianprops) +axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) + +axes[0, 2].boxplot(data, whis='range') +axes[0, 2].set_title('whis="range"', fontsize=fs) + +axes[1, 0].boxplot(data, meanprops=meanpointprops, meanline=False, + showmeans=True) +axes[1, 0].set_title('Custom mean as point', fontsize=fs) + +axes[1, 1].boxplot(data, meanprops=meanlineprops, meanline=True, showmeans=True) +axes[1, 1].set_title('Custom mean as line', fontsize=fs) + +axes[1, 2].boxplot(data, whis=[15, 85]) +axes[1, 2].set_title('whis=[15, 85] #percentiles', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +fig.suptitle("I never said they'd be pretty") +plt.show() diff --git a/examples/statistics/bxp_demo.py b/examples/statistics/bxp_demo.py new file mode 100644 index 000000000000..0d413545bc1e --- /dev/null +++ b/examples/statistics/bxp_demo.py @@ -0,0 +1,81 @@ +""" +Demo of the new boxplot drawer function +""" + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.cbook as cbook + +# fake data +np.random.seed(937) +data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75) +labels = list('ABCD') + +# compute the boxplot stats +stats = cbook.boxplot_stats(data, labels=labels, bootstrap=10000) +# After we've computed the stats, we can go through and change anything. +# Just to prove it, I'll set the median of each set to the median of all +# the data, and double the means +for n in range(len(stats)): + stats[n]['med'] = np.median(data) + stats[n]['mean'] *= 2 + +print(stats[0].keys()) +fs = 10 # fontsize + +# demonstrate how to toggle the display of different elements: +fig, axes = plt.subplots(nrows=2, ncols=3) +axes[0, 0].bxp(stats) +axes[0, 0].set_title('Default', fontsize=fs) + +axes[0, 1].bxp(stats, showmeans=True) +axes[0, 1].set_title('showmeans=True', fontsize=fs) + +axes[0, 2].bxp(stats, showmeans=True, meanline=True) +axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) + +axes[1, 0].bxp(stats, showbox=False, showcaps=False) +axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) + +axes[1, 1].bxp(stats, shownotches=True) +axes[1, 1].set_title('notch=True', fontsize=fs) + +axes[1, 2].bxp(stats, showfliers=False) +axes[1, 2].set_title('showfliers=False', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +plt.show() + + +# demonstrate how to customize the display different elements: +boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod') +flierprops = dict(marker='o', markerfacecolor='green', markersize=12, + linestyle='none') +medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick') +meanpointprops = dict(marker='D', markeredgecolor='black', + markerfacecolor='firebrick') +meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') + +fig, axes = plt.subplots(nrows=2, ncols=2) +axes[0, 0].bxp(stats, boxprops=boxprops) +axes[0, 0].set_title('Custom boxprops', fontsize=fs) + +axes[0, 1].bxp(stats, flierprops=flierprops, medianprops=medianprops) +axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) + +axes[1, 0].bxp(stats, meanprops=meanpointprops, meanline=False, + showmeans=True) +axes[1, 0].set_title('Custom mean as point', fontsize=fs) + +axes[1, 1].bxp(stats, meanprops=meanlineprops, meanline=True, showmeans=True) +axes[1, 1].set_title('Custom mean as line', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +fig.suptitle("I never said they'd be pretty") +plt.show() From 13937e98218144dcd0e0da91b9de1c9869c55855 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 16 Dec 2013 13:00:37 -0800 Subject: [PATCH 16/43] DOC: impoved example boxplot figure layout and confirmed that they build with docs --- examples/statistics/boxplot_demo.py | 20 +++++++++++--------- examples/statistics/bxp_demo.py | 16 +++++++++------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/statistics/boxplot_demo.py b/examples/statistics/boxplot_demo.py index 7bc72e30cdf0..f810f6700d7f 100644 --- a/examples/statistics/boxplot_demo.py +++ b/examples/statistics/boxplot_demo.py @@ -12,7 +12,7 @@ fs = 10 # fontsize # demonstrate how to toggle the display of different elements: -fig, axes = plt.subplots(nrows=2, ncols=3) +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6)) axes[0, 0].boxplot(data, labels=labels) axes[0, 0].set_title('Default', fontsize=fs) @@ -20,13 +20,13 @@ axes[0, 1].set_title('showmeans=True', fontsize=fs) axes[0, 2].boxplot(data, labels=labels, showmeans=True, meanline=True) -axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) +axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs) axes[1, 0].boxplot(data, labels=labels, showbox=False, showcaps=False) -axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) +axes[1, 0].set_title('Tufte Style \n(showbox=False,\nshowcaps=False)', fontsize=fs) axes[1, 1].boxplot(data, labels=labels, notch=True, bootstrap=10000) -axes[1, 1].set_title('notch=True, bootstrap=10000', fontsize=fs) +axes[1, 1].set_title('notch=True,\nbootstrap=10000', fontsize=fs) axes[1, 2].boxplot(data, labels=labels, showfliers=False) axes[1, 2].set_title('showfliers=False', fontsize=fs) @@ -35,6 +35,7 @@ ax.set_yscale('log') ax.set_yticklabels([]) +fig.subplots_adjust(hspace=0.4) plt.show() @@ -47,29 +48,30 @@ markerfacecolor='firebrick') meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') -fig, axes = plt.subplots(nrows=2, ncols=3) +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6)) axes[0, 0].boxplot(data, boxprops=boxprops) axes[0, 0].set_title('Custom boxprops', fontsize=fs) axes[0, 1].boxplot(data, flierprops=flierprops, medianprops=medianprops) -axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) +axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs) axes[0, 2].boxplot(data, whis='range') axes[0, 2].set_title('whis="range"', fontsize=fs) axes[1, 0].boxplot(data, meanprops=meanpointprops, meanline=False, showmeans=True) -axes[1, 0].set_title('Custom mean as point', fontsize=fs) +axes[1, 0].set_title('Custom mean\nas point', fontsize=fs) axes[1, 1].boxplot(data, meanprops=meanlineprops, meanline=True, showmeans=True) -axes[1, 1].set_title('Custom mean as line', fontsize=fs) +axes[1, 1].set_title('Custom mean\nas line', fontsize=fs) axes[1, 2].boxplot(data, whis=[15, 85]) -axes[1, 2].set_title('whis=[15, 85] #percentiles', fontsize=fs) +axes[1, 2].set_title('whis=[15, 85]\n#percentiles', fontsize=fs) for ax in axes.flatten(): ax.set_yscale('log') ax.set_yticklabels([]) fig.suptitle("I never said they'd be pretty") +fig.subplots_adjust(hspace=0.4) plt.show() diff --git a/examples/statistics/bxp_demo.py b/examples/statistics/bxp_demo.py index 0d413545bc1e..74d60b0b27bb 100644 --- a/examples/statistics/bxp_demo.py +++ b/examples/statistics/bxp_demo.py @@ -24,7 +24,7 @@ fs = 10 # fontsize # demonstrate how to toggle the display of different elements: -fig, axes = plt.subplots(nrows=2, ncols=3) +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6)) axes[0, 0].bxp(stats) axes[0, 0].set_title('Default', fontsize=fs) @@ -32,10 +32,10 @@ axes[0, 1].set_title('showmeans=True', fontsize=fs) axes[0, 2].bxp(stats, showmeans=True, meanline=True) -axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) +axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs) axes[1, 0].bxp(stats, showbox=False, showcaps=False) -axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) +axes[1, 0].set_title('Tufte Style\n(showbox=False,\nshowcaps=False)', fontsize=fs) axes[1, 1].bxp(stats, shownotches=True) axes[1, 1].set_title('notch=True', fontsize=fs) @@ -47,6 +47,7 @@ ax.set_yscale('log') ax.set_yticklabels([]) +fig.subplots_adjust(hspace=0.4) plt.show() @@ -59,23 +60,24 @@ markerfacecolor='firebrick') meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') -fig, axes = plt.subplots(nrows=2, ncols=2) +fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(6,6)) axes[0, 0].bxp(stats, boxprops=boxprops) axes[0, 0].set_title('Custom boxprops', fontsize=fs) axes[0, 1].bxp(stats, flierprops=flierprops, medianprops=medianprops) -axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) +axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs) axes[1, 0].bxp(stats, meanprops=meanpointprops, meanline=False, showmeans=True) -axes[1, 0].set_title('Custom mean as point', fontsize=fs) +axes[1, 0].set_title('Custom mean\nas point', fontsize=fs) axes[1, 1].bxp(stats, meanprops=meanlineprops, meanline=True, showmeans=True) -axes[1, 1].set_title('Custom mean as line', fontsize=fs) +axes[1, 1].set_title('Custom mean\nas line', fontsize=fs) for ax in axes.flatten(): ax.set_yscale('log') ax.set_yticklabels([]) fig.suptitle("I never said they'd be pretty") +fig.subplots_adjust(hspace=0.4) plt.show() From eed0ce0593e7b3ace4c341cee62b7b6ad2a34943 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Thu, 28 Nov 2013 21:52:11 -0800 Subject: [PATCH 17/43] REF/TST: split up boxplot function into a calculator (cbook.boxplot_stats) and a drawer (axes.bxp). Existing function now relies on these two functions. --- lib/matplotlib/axes/_axes.py | 422 +++++++++--------- lib/matplotlib/cbook.py | 139 ++++++ .../baseline_images/test_axes/boxplot.pdf | Bin 5162 -> 5231 bytes .../baseline_images/test_axes/boxplot.png | Bin 11891 -> 11065 bytes .../baseline_images/test_axes/boxplot.svg | 204 ++++----- .../test_axes/boxplot_no_inverted_whisker.png | Bin 1889 -> 1775 bytes .../test_axes/boxplot_with_CIarray.png | Bin 2369 -> 1870 bytes .../test_axes/bxp_baseline.png | Bin 0 -> 1955 bytes .../test_axes/bxp_customoutlier.png | Bin 0 -> 2159 bytes .../test_axes/bxp_horizontal.png | Bin 0 -> 2161 bytes .../baseline_images/test_axes/bxp_nocaps.png | Bin 0 -> 1888 bytes .../test_axes/bxp_withmean_custompoint.png | Bin 0 -> 2159 bytes .../test_axes/bxp_withmean_line.png | Bin 0 -> 2096 bytes .../test_axes/bxp_withmean_point.png | Bin 0 -> 2174 bytes .../test_axes/bxp_withnotch.png | Bin 0 -> 5119 bytes lib/matplotlib/tests/test_axes.py | 126 ++++++ lib/matplotlib/tests/test_cbook.py | 96 +++- 17 files changed, 656 insertions(+), 331 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_baseline.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_point.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4717a6ecf64f..ce4d4bb933b7 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2883,245 +2883,221 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, .. plot:: pyplots/boxplot_demo.py """ - def bootstrapMedian(data, N=5000): - # determine 95% confidence intervals of the median - M = len(data) - percentile = [2.5, 97.5] - estimate = np.zeros(N) - for n in range(N): - bsIndex = np.random.random_integers(0, M - 1, M) - bsData = data[bsIndex] - estimate[n] = mlab.prctile(bsData, 50) - CI = mlab.prctile(estimate, percentile) - return CI - - def computeConfInterval(data, med, iq, bootstrap): - if bootstrap is not None: - # Do a bootstrap estimate of notch locations. - # get conf. intervals around median - CI = bootstrapMedian(data, N=bootstrap) - notch_min = CI[0] - notch_max = CI[1] - else: - # Estimate notch locations using Gaussian-based - # asymptotic approximation. - # - # For discussion: McGill, R., Tukey, J.W., - # and Larsen, W.A. (1978) "Variations of - # Boxplots", The American Statistician, 32:12-16. - N = len(data) - notch_min = med - 1.57 * iq / np.sqrt(N) - notch_max = med + 1.57 * iq / np.sqrt(N) - return notch_min, notch_max - - if not self._hold: - self.cla() - holdStatus = self._hold - whiskers, caps, boxes, medians, fliers = [], [], [], [], [] + bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap) + if sym == 'b+': + flierprops = dict(linestyle='none', marker='+', + markeredgecolor='blue') + else: + flierprops = sym - # convert x to a list of vectors - if hasattr(x, 'shape'): - if len(x.shape) == 1: - if hasattr(x[0], 'shape'): - x = list(x) - else: - x = [x, ] - elif len(x.shape) == 2: - nr, nc = x.shape - if nr == 1: - x = [x] - elif nc == 1: - x = [x.ravel()] - else: - x = [x[:, i] for i in xrange(nc)] + # replace medians if necessary: + if usermedians is not None: + if len(usermedians) != len(bxpstats): + medmsg = 'usermedians length not compatible with x' + raise ValueError(medmsg) else: - raise ValueError("input x can have no more than 2 dimensions") - if not hasattr(x[0], '__len__'): - x = [x] - col = len(x) + for stats, med in zip(bxpstats, usermedians): + if med is not None: + stats['med'] = med - # sanitize user-input medians - msg1 = "usermedians must either be a list/tuple or a 1d array" - msg2 = "usermedians' length must be compatible with x" - if usermedians is not None: - if hasattr(usermedians, 'shape'): - if len(usermedians.shape) != 1: - raise ValueError(msg1) - elif usermedians.shape[0] != col: - raise ValueError(msg2) - elif len(usermedians) != col: - raise ValueError(msg2) - - #sanitize user-input confidence intervals - msg1 = "conf_intervals must either be a list of tuples or a 2d array" - msg2 = "conf_intervals' length must be compatible with x" - msg3 = "each conf_interval, if specificied, must have two values" if conf_intervals is not None: - if hasattr(conf_intervals, 'shape'): - if len(conf_intervals.shape) != 2: - raise ValueError(msg1) - elif conf_intervals.shape[0] != col: - raise ValueError(msg2) - elif conf_intervals.shape[1] != 2: - raise ValueError(msg3) + if len(conf_intervals) != len(bxpstats): + cimsg = 'conf_intervals length not compatible with x' + raise ValueError(cimsg) else: - if len(conf_intervals) != col: - raise ValueError(msg2) - for ci in conf_intervals: - if ci is not None and len(ci) != 2: - raise ValueError(msg3) + for stats, ci in zip(bxpstats, conf_intervals): + if ci is not None: + if len(ci) != 2: + cimsg2 = 'each confidence interval must '\ + 'have two values' + raise ValueError(cimsg2) + else: + if ci[0] is not None: + stats['cilo'] = ci[0] + if ci[1] is not None: + stats['cihi'] = ci[1] + + artists = self.bxp(bxpstats, positions=positions, widths=widths, + vert=vert, patch_artist=patch_artist, + shownotches=notch, showmeans=False, + showcaps=True, boxprops=None, + flierprops=flierprops, medianprops=None, + meanprops=None, meanline=False) + return artists + + def bxp(self, bxpstats, positions=None, widths=None, vert=True, + patch_artist=False, shownotches=False, showmeans=False, + showcaps=True, boxprops=None, flierprops=None, + medianprops=None, meanprops=None, meanline=False): + + # lists of artists to be output + whiskers, caps, boxes, medians, means, fliers = [], [], [], [], [], [] + + # plotting properties + if boxprops is None: + boxprops = dict(linestyle='-', color='black') + + if flierprops is None: + flierprops = dict(linestyle='none', marker='+', + markeredgecolor='blue') + + if medianprops is None: + medianprops = dict(linestyle='-', color='blue') + + if meanprops is None: + if meanline: + meanprops = dict(linestyle='--', color='red') + else: + meanprops = dict(linestyle='none', markerfacecolor='red', + marker='s') + + def to_vc(xs, ys): + # convert arguments to verts and codes + verts = [] + #codes = [] + for xi, yi in zip(xs, ys): + verts.append((xi, yi)) + verts.append((0, 0)) # ignored + codes = [mpath.Path.MOVETO] + \ + [mpath.Path.LINETO] * (len(verts) - 2) + \ + [mpath.Path.CLOSEPOLY] + return verts, codes + + def patch_list(xs, ys, **kwargs): + verts, codes = to_vc(xs, ys) + path = mpath.Path(verts, codes) + patch = mpatches.PathPatch(path, **kwargs) + self.add_artist(patch) + return [patch] + + # vertical or horizontal plot? + if vert: + def doplot(*args, **kwargs): + return self.plot(*args, **kwargs) - # get some plot info + def dopatch(xs, ys, **kwargs): + return patch_list(xs, ys, **kwargs) + + else: + def doplot(*args, **kwargs): + shuffled = [] + for i in xrange(0, len(args), 2): + shuffled.extend([args[i + 1], args[i]]) + return self.plot(*shuffled, **kwargs) + + def dopatch(xs, ys, **kwargs): + xs, ys = ys, xs # flip X, Y + return patch_list(xs, ys, **kwargs) + + # input validation + N = len(bxpstats) + datashape_message = "List of boxplot statistics and `{0}` " \ + "value must have same length" if positions is None: - positions = list(xrange(1, col + 1)) + positions = list(xrange(1, N + 1)) + elif len(positions) != N: + raise ValueError(datashape_message.format("positions")) + if widths is None: distance = max(positions) - min(positions) - widths = min(0.15 * max(distance, 1.0), 0.5) - if isinstance(widths, float) or isinstance(widths, int): - widths = np.ones((col,), float) * widths - - # loop through columns, adding each to plot - self.hold(True) - for i, pos in enumerate(positions): - d = np.ravel(x[i]) - row = len(d) - if row == 0: - # no data, skip this position - continue - - # get median and quartiles - q1, med, q3 = mlab.prctile(d, [25, 50, 75]) + widths = [min(0.15 * max(distance, 1.0), 0.5)] * N + elif len(widths) != len(bxpstats): + raise ValueError(datashape_message.format("widths")) - # replace with input medians if available - if usermedians is not None: - if usermedians[i] is not None: - med = usermedians[i] - - # get high extreme - iq = q3 - q1 - hi_val = q3 + whis * iq - wisk_hi = np.compress(d <= hi_val, d) - if len(wisk_hi) == 0 or np.max(wisk_hi) < q3: - wisk_hi = q3 - else: - wisk_hi = max(wisk_hi) - - # get low extreme - lo_val = q1 - whis * iq - wisk_lo = np.compress(d >= lo_val, d) - if len(wisk_lo) == 0 or np.min(wisk_lo) > q1: - wisk_lo = q1 - else: - wisk_lo = min(wisk_lo) - - # get fliers - if we are showing them - flier_hi = [] - flier_lo = [] - flier_hi_x = [] - flier_lo_x = [] - if len(sym) != 0: - flier_hi = np.compress(d > wisk_hi, d) - flier_lo = np.compress(d < wisk_lo, d) - flier_hi_x = np.ones(flier_hi.shape[0]) * pos - flier_lo_x = np.ones(flier_lo.shape[0]) * pos - - # get x locations for fliers, whisker, whisker cap and box sides - box_x_min = pos - widths[i] * 0.5 - box_x_max = pos + widths[i] * 0.5 - - wisk_x = np.ones(2) * pos - - cap_x_min = pos - widths[i] * 0.25 - cap_x_max = pos + widths[i] * 0.25 - cap_x = [cap_x_min, cap_x_max] - - # get y location for median - med_y = [med, med] - - # calculate 'notch' plot - if notch: - # conf. intervals from user, if available - if (conf_intervals is not None and - conf_intervals[i] is not None): - notch_max = np.max(conf_intervals[i]) - notch_min = np.min(conf_intervals[i]) - else: - notch_min, notch_max = computeConfInterval(d, med, iq, - bootstrap) - - # make our notched box vectors - box_x = [box_x_min, box_x_max, box_x_max, cap_x_max, box_x_max, - box_x_max, box_x_min, box_x_min, cap_x_min, box_x_min, - box_x_min] - box_y = [q1, q1, notch_min, med, notch_max, q3, q3, notch_max, - med, notch_min, q1] - # make our median line vectors - med_x = [cap_x_min, cap_x_max] - med_y = [med, med] - # calculate 'regular' plot - else: - # make our box vectors - box_x = [box_x_min, box_x_max, box_x_max, box_x_min, box_x_min] - box_y = [q1, q1, q3, q3, q1] - # make our median line vectors - med_x = [box_x_min, box_x_max] - - def to_vc(xs, ys): - # convert arguments to verts and codes - verts = [] - #codes = [] - for xi, yi in zip(xs, ys): - verts.append((xi, yi)) - verts.append((0, 0)) # ignored - codes = [mpath.Path.MOVETO] + \ - [mpath.Path.LINETO] * (len(verts) - 2) + \ - [mpath.Path.CLOSEPOLY] - return verts, codes - - def patch_list(xs, ys): - verts, codes = to_vc(xs, ys) - path = mpath.Path(verts, codes) - patch = mpatches.PathPatch(path) - self.add_artist(patch) - return [patch] - - # vertical or horizontal plot? - if vert: - - def doplot(*args): - return self.plot(*args) - - def dopatch(xs, ys): - return patch_list(xs, ys) - else: - - def doplot(*args): - shuffled = [] - for i in xrange(0, len(args), 3): - shuffled.extend([args[i + 1], args[i], args[i + 2]]) - return self.plot(*shuffled) + if not self._hold: + self.cla() - def dopatch(xs, ys): - xs, ys = ys, xs # flip X, Y - return patch_list(xs, ys) + holdStatus = self._hold - if patch_artist: - median_color = 'k' + for pos, width, stats in zip(positions, widths, bxpstats): + # outliers coords + flier_x = np.ones(len(stats['outliers'])) * pos + flier_y = stats['outliers'] + + # whisker coords + whisker_x = np.ones(2) * pos + whiskerlo_y = np.array([stats['q1'], stats['whislo']]) + whiskerhi_y = np.array([stats['q3'], stats['whishi']]) + + # cap coords + cap_left = pos - width * 0.25 + cap_right = pos + width * 0.25 + cap_x = np.array([cap_left, cap_right]) + cap_lo = np.ones(2) * stats['whislo'] + cap_hi = np.ones(2) * stats['whishi'] + + # box and median coords + box_left = pos - width * 0.5 + box_right = pos + width * 0.5 + med_y = [stats['med'], stats['med']] + + # notched boxes + if shownotches: + box_x = [box_left, box_right, box_right, cap_right, box_right, + box_right, box_left, box_left, cap_left, box_left, + box_left] + box_y = [stats['q1'], stats['q1'], stats['cilo'], + stats['med'], stats['cihi'], stats['q3'], + stats['q3'], stats['cihi'], stats['med'], + stats['cilo'], stats['q1']] + med_x = cap_x + + # plain boxes else: - median_color = 'r' + box_x = [box_left, box_right, box_right, box_left, box_left] + box_y = [stats['q1'], stats['q1'], stats['q3'], stats['q3'], + stats['q1']] + med_x = [box_left, box_right] - whiskers.extend(doplot(wisk_x, [q1, wisk_lo], 'b--', - wisk_x, [q3, wisk_hi], 'b--')) - caps.extend(doplot(cap_x, [wisk_hi, wisk_hi], 'k-', - cap_x, [wisk_lo, wisk_lo], 'k-')) + # draw the box: if patch_artist: - boxes.extend(dopatch(box_x, box_y)) + boxes.extend(dopatch( + box_x, box_y, **boxprops + )) else: - boxes.extend(doplot(box_x, box_y, 'b-')) - - medians.extend(doplot(med_x, med_y, median_color + '-')) - fliers.extend(doplot(flier_hi_x, flier_hi, sym, - flier_lo_x, flier_lo, sym)) + boxes.extend(doplot( + box_x, box_y, **boxprops + )) + + # draw the whiskers + whiskers.extend(doplot( + whisker_x, whiskerlo_y, **boxprops + )) + whiskers.extend(doplot( + whisker_x, whiskerhi_y, **boxprops + )) + + # maybe draw the caps: + if showcaps: + caps.extend(doplot( + cap_x, cap_lo, **boxprops + )) + caps.extend(doplot( + cap_x, cap_hi, **boxprops + )) + + # draw the medians + medians.extend(doplot( + med_x, med_y, **medianprops + )) + + # maybe draw the means + if showmeans: + if meanline: + means.extend(doplot( + [box_left, box_right], + [stats['mean'], stats['mean']], + **meanprops + )) + else: + means.extend(doplot( + [pos], [stats['mean']], **meanprops + )) + + # draw the outliers + fliers.extend(doplot( + flier_x, flier_y, **flierprops + )) # fix our axes/ticks up a little if vert: diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index c5e96a5bda5f..ae5e91049542 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1846,6 +1846,145 @@ def delete_masked_points(*args): return margs +def boxplot_stats(X, whis=1.5, bootstrap=None): + ''' + Returns list of dictionaries of staticists to be use to draw a series of + box and whisker plots. See the `Returns` section below to the required + keys of the dictionary. Users can skip this function and pass a user- + defined set of dictionaries to the new `axes.bxp` method instead of + relying on MPL to do the calcs. + + Parameters + ---------- + X : array-like + Data that will be represented in the boxplots. Should have 2 or fewer + dimensions. + + whis : float (default = 1.5) + Determines the reach of the whiskers past the first and third + quartiles (e.g., Q3 + whis*IQR). Beyone the whiskers, data are + considers outliers and are plotted as individual points. Set + this to an unreasonably high value to force the whiskers to + show the min and max data. (IQR = interquartile range, Q3-Q1) + + bootstrap : int or None (default) + Number of times the confidence intervals around the median should + be bootstrapped (percentile method). + + Returns + ------- + bxpstats : A list of dictionaries containing the results for each column + of data. Keys are as + ''' + + def _bootstrap_median(data, N=5000): + # determine 95% confidence intervals of the median + M = len(data) + percentiles = [2.5, 97.5] + + # initialize the array of estimates + estimate = np.empty(N) + for n in range(N): + bsIndex = np.random.random_integers(0, M - 1, M) + bsData = data[bsIndex] + estimate[n] = np.percentile(bsData, 50) + + CI = np.percentile(estimate, percentiles) + return CI + + def _compute_conf_interval(data, med, iqr, bootstrap): + if bootstrap is not None: + # Do a bootstrap estimate of notch locations. + # get conf. intervals around median + CI = _bootstrap_median(data, N=bootstrap) + notch_min = CI[0] + notch_max = CI[1] + else: + # Estimate notch locations using Gaussian-based + # asymptotic approximation. + # + # For discussion: McGill, R., Tukey, J.W., + # and Larsen, W.A. (1978) "Variations of + # Boxplots", The American Statistician, 32:12-16. + N = len(data) + notch_min = med - 1.57 * iqr / np.sqrt(N) + notch_max = med + 1.57 * iqr / np.sqrt(N) + + return notch_min, notch_max + + # output is a list of dicts + bxpstats = [] + + # convert X to a list of lists + if hasattr(X, 'shape'): + # one item + if len(X.shape) == 1: + if hasattr(X[0], 'shape'): + X = list(X) + else: + X = [X, ] + + # several items + elif len(X.shape) == 2: + nrows, ncols = X.shape + if nrows == 1: + X = [X] + elif ncols == 1: + X = [X.ravel()] + else: + X = [X[:, i] for i in xrange(ncols)] + else: + raise ValueError("input `X` must have 2 or fewer dimensions") + + if not hasattr(X[0], '__len__'): + X = [X] + + ncols = len(X) + for ii, x in enumerate(X, start=0): + stats = {} + + # arithmetic mean + stats['mean'] = np.mean(x) + + # medians and quartiles + stats['q1'], stats['med'], stats['q3'] = \ + np.percentile(x, [25, 50, 75]) + + # interquartile range + stats['iqr'] = stats['q3'] - stats['q1'] + + # conf. interval around median + stats['cilo'], stats['cihi'] = _compute_conf_interval( + x, stats['med'], stats['iqr'], bootstrap + ) + + # highest non-outliers + hival = stats['q3'] + whis * stats['iqr'] + wiskhi = np.compress(x <= hival, x) + if len(wiskhi) == 0 or np.max(wiskhi) < stats['q3']: + stats['whishi'] = stats['q3'] + else: + stats['whishi'] = max(wiskhi) + + # get low extreme + loval = stats['q1'] - whis * stats['iqr'] + wisklo = np.compress(x >= loval, x) + if len(wisklo) == 0 or np.min(wisklo) > stats['q1']: + stats['whislo'] = stats['q1'] + else: + stats['whislo'] = min(wisklo) + + # compute a single array of outliers + stats['outliers'] = np.hstack([ + np.compress(x < stats['whislo'], x), + np.compress(x > stats['whishi'], x) + ]) + + bxpstats.append(stats) + + return bxpstats + + # FIXME I don't think this is used anywhere def unmasked_index_ranges(mask, compressed=True): """ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.pdf index 1ef558832b9169958465afbcc03324068a1b046e..6603608c683246796da6e8ad0f3d061b0c176fa0 100644 GIT binary patch delta 1305 zcmZ3b@m^!Y7RGvELxH`&!+&tJTglb`zQMKP$XbDel2f+{sJeQ#{`xLieeCyC-{?si zUDvFQt>0JLoWIAqQ%FI6$u#Xh_TCe&J3Y=&uH~tSy}o)goBOQw`;Y96hDFAaE9u#rB|)y)|=|He0=?B-ih$XU%ltc98xg;b6zND)m`VaYp2Sm z`b#}dPMyoddp+j;gx#0i`zyj_a+}{BI=$m)wES-NoR$_(7Mo4+cfK5^>Dt}FGXHDQ+7n1I;uV) z;mN0c@5C)%C+yjBfVb#mo5CPBC+@G-nMsJf~A6F z8fQm-_-CtJ#On3vptXA?W8q!y6A~re`Ib>}Q&da*T%C?w)wq89>=LCDEag3qE=}#f z+B(6(DZ0_FN9*i`euIOLZr^=({OGgqkMFDheAvI^;Qc8!542sCzL~svX)gZp{oeIH z3hqzunJ^a5i>f82AzQH2HWMH!S8_Pd#UwubI1sfX$eP>4n z0|ot%%7Rn{eb@YAeGX?3Ld|Sx89>Wx4Lx?Q0tBfp74bW{fGBYqo z7c((3v_Ka#G&0Aq!_d?e>;eM=FND_(EzQi(H5(ZkV>sW)1g06G*TmG!Wb$ocS1xly MBQ8}{SARDy01jpb#Q*>R delta 1245 zcmaE_u}WjZ7RLH31_FD3hyUPc-!<=Jc+J`duRBp;1r9O-xs6%dSGcaIpD$VcMrC&F z#@nuzDK?MKRL?uJS@^tvq)OW;D8)tepAh&f+}(O8LrJ{A#b3NB{M-7hn*syjDNIbjfS(-h7kE zjJ35Z6ADa(iwq7v?`cj=lGc6alF8v*8xa00gs0?7$ObdVZ!fIb+A=Es{y+BMPmDrZ z>pEMHN~0Nb;^ujT-4vGH_iEOPwMK5nZ!}b+vgh3YwtlvJuG%f;r=>517H)fbphRd< zCuhU=d*WRo7!^gas0E)`mH(%xtDq-zWVo8R3=PbLv)v1y!4Ti*3HeI3d*lq z*-xD3Gh=G)29t}f(Nn6CSAUn&J{WebS58T_rcAb~{g(leL_-OT@m()YYn9 zG*+=`=Fyco8%*bU`W0;doApTl{Ps;-|LETPQ2yCIthRW{$EO^xGNry;3p^geTXoBp z?P9{)C951a`d?3A)s|Wl;o59kdojUuH(T~PLtpFuCljVRK60M+>~vV*xoi6hdFJ^? z^}R|j-FWHwdk5xMS7UDq6m1i1E}M35bKKn(;UDUw*1!9Dd#ct1CF91)j?I%-al}Z! zZT);rn%h|`V@B+%1`BqX{R)$GCQtU=r}pFWq3{0Oj8!3bbkbu|-}|o8XzsiyHYb1f z+Nr_emkbYW)+r5|c`jy7s!`Vo8zEWm$v@BbiaoXbU-A3P*PPFvAD++u`)vBIMXQC` zt!}iP+A~Y}t62T2JI7zxeX^9YnPfB1!_MHm*S;CTpM-=y+3xDMVR`jQ`R3uwH?L35 zSv2QVZ)IutJ>5+iXFQHq7C0C=FbKc=+?cDDD!#X~(=~Krm*v4p6Bc}Z!K6RIMT_m2 z&wNp3IUUBi&CA-d`WkdY7>~cub5OT(7kbGeP|nabFZi~nT7!^~)&aS@tS@3GDo88O zd0p_O*^qViUxf>G`<8yMEcot!Ovm6~(uC;0tQA+L=T2s16XrHlFi^-(%HpyxG}!!s z^!0NSOA2!GOL8)k^orB; z@={AC#|XLBn;07@7=VC6o&pz`VPIlpX^tUgY-EWkW@>;bW)5^3nt8^S7vxF_)^UtG^o;07R+p6aWAK diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot.png index 797d133e371e5782b542523fff94dbd14c609715..f17404f5ad484eed914be7dbad3331bab2528d1f 100644 GIT binary patch literal 11065 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 G?f(E}<3XAL literal 11891 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 Ph|c$h=L^pMc @@ -30,42 +30,42 @@ z +M166.86 236.45 +L200.34 236.45 +L200.34 222.672 +L191.97 216 +L200.34 209.328 +L200.34 195.55 +L166.86 195.55 +L166.86 209.328 +L175.23 216 +L166.86 222.672 +L166.86 236.45" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M183.6 236.45 +L183.6 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M183.6 195.55 +L183.6 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L191.97 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M175.23 175.68 +L191.97 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L191.97 216" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> @@ -73,86 +73,78 @@ L191.97 216" style="fill:none;stroke:#ff0000;"/> M-3 0 L3 0 M0 3 -L0 -3" id="me594928b4b" style="stroke:#0000ff;stroke-linecap:butt;stroke-width:0.5;"/> +L0 -3" id="md4acab13e0" style="stroke:#0000ff;stroke-width:0.5;"/> - + + - - - + +L406.8 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +L406.8 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M398.43 256.32 +L415.17 256.32" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M398.43 175.68 +L415.17 175.68" style="fill:none;stroke:#000000;stroke-linecap:square;"/> +M398.43 216 +L415.17 216" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/> - - - - - - - - - + + - + +L0 -4" id="m93b0483c22" style="stroke:#000000;stroke-width:0.5;"/> - + - + +L0 4" id="m741efc42ff" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -173,20 +165,20 @@ L12.4062 0 z " id="BitstreamVeraSans-Roman-31"/> - + - + - + - + - + @@ -216,7 +208,7 @@ Q49.8594 40.875 45.4062 35.4062 Q44.1875 33.9844 37.6406 27.2188 Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + @@ -224,24 +216,24 @@ Q31.1094 20.4531 19.1875 8.29688" id="BitstreamVeraSans-Roman-32"/> - + +L4 0" id="m728421d6d4" style="stroke:#000000;stroke-width:0.5;"/> - + - + +L-4 0" id="mcb0005524f" style="stroke:#000000;stroke-width:0.5;"/> - + @@ -305,7 +297,7 @@ Q6.59375 17.9688 6.59375 36.375 Q6.59375 54.8281 13.0625 64.5156 Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + @@ -313,19 +305,19 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + - + - + @@ -333,19 +325,19 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + - + - + @@ -353,75 +345,75 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -431,22 +423,22 @@ Q19.5312 74.2188 31.7812 74.2188" id="BitstreamVeraSans-Roman-30"/> +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +L518.4 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +L518.4 388.8" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> +L72 43.2" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-linejoin:miter;"/> diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_no_inverted_whisker.png index ea43a079599c3210bae4d3616d56a7e9b1cb5362..cba447aca198b321a617bc92629d3dd83bf21495 100644 GIT binary patch literal 1775 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png index c3136d0e13e709eb49178d0a1c64cb250664fa28..614c60487d300e706ce3fdada6da1163c31f2fa0 100644 GIT binary patch literal 1870 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*+7b=*vT`50|;t3QaXTq&H|6f zVg?2_H4tW;FKnd@WPI>+aSW-L^Y-q>Y>`Zv;}6Z-eRr+p_q2&v+p-{aS9Z>ptyiXW z2~K(=zc7rGF?@@U>?#XlRhAmHN*Dbt(fr=^_(743TKStdd!GEZ5ok%h_UF*- z?~i*@)VMFdEQw-Z`g`R&14FH#gTgchrj0BD9jXluI*cqyoC*To42=;?97lvmkP4MQ zuq@N-vPo?}&;)^)lUa{GQDBy&)<>_n;T!B zulnxIpq-+evhm*ELQWpyd>mTKaC@6>?{e2eTQ8aTivHg9i-D96C7yll-zU-l^M z2+O*8`5fs%NMgW4oZ)-gwI? znJ;F2UDxiNfHT>AYQ9jsZU6pwyV_qRFAL@E>vW={qwQ_dN=vue)cvUlTYQo6b*wq# zoXzLw+fSc6cdmE;?YnpPelc5dF(bt}~Y~t zzvcgbzn|f^Jk>^S`qZgYCH>sQ*OcF7DL8lS+O<=kJ{8@(b4Ta-^XLD6{soe$#l@TN z>?&QoX6@Qi1I5iLdta+H*j9Xb5f~XCuYZ1?t@iTe%fsvLrx-tJ9- zQ`*U@>#Kh;Y|D+Fwd`ee&&=J$&;2C(k3aqT`ufusFJ4@F+~3!CYPNa)soUH0Prtpr z{W;ih{j6Ebx@zBs?cjRz{(nr}@3+T~|M~Y@|LL1I*MIW3EMHUZx@_w0Ten_SxA@ik z{Z;zw$;rttff4YjT5a;lFE1}Iznp)6U#W#;?1p>)*S-H6`&actX-P#z#J#=M;dV7Y z0;Wxyw(ifqrAwE7`uF#@kzD`jhYu6mg^j$g?T%xzNUyH0e){FhmN#$SOgVb==>I?e zfaIpln}gLRd)~Z#`*i}?PreM#-#mG;WKYG%MLIenaGD2LM9A>{mSdi=FzVYDW1^tv<*(a zm!}+Kev}mP{$Ggvfo-|9#Rp7-y}zukmpXIvW@P2pSD}(TY_ literal 2369 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 JcV`3V_+Rb5N9X_m literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_customoutlier.png new file mode 100644 index 0000000000000000000000000000000000000000..53bb0c2bd6e645a6b202870b3d5fb86c6fb1eb32 GIT binary patch literal 2159 zcmb_ec~BF17VaD*Ed?Yf15u8I;DUlmHeLv92nH0C1x5iyWD+P;1}K3E3@8Z(MMRN9 z0YN3eFyd5kDmZdO+$=#wAP@z#fN%&&l$*#Aj%-K98m-ztrfT+9~Whc(r=r~?3?>Fnfi3;%-LogJO%!AGzAhcLuKqB>Wl z)dkDjWNiWfptiGvy+?G;M1NDTuVgcC=J3Pqos1&m<0JVRlVi@bimf!g(9PD(%Bwkb z7u|JZ>OBY;Z|jUMQypa4r-Ywt-;sSa;d=F>$JCE-ha#LttqEx78cBU>$J~+& zD*S-twzPO|%}P=d>D!}5weLBV2T(2GV8-7#RPX;iWKuXu%|zWK$B!bwYg_Bor&IF_ zaB(Bs=6qkZw!Xgp6{Dz{(}x^1V*9#Tq2ie5BfGL%d&OeByw_6USZP8RmikorTXVHy z`zo7U_lFJ`tn}_&8faWdGfj6+AIWvb^D3KkXvOnMVS>Z5|t)H1ZrUgFANC(Jr%wl57R|oLzlY!dRLC2 zk~Se)7kLGaluP3=F))v%A!X3Foh>N z_gnbtyHC}z6Bk(v<8{wc^M-z4FgcQ5)|@hZI8^yz;;9**VBUyZz2C$ZDRk~_;xRpP zb;_mV&C=k;YCw&#hBzap>=f|JaoGz3tLSenb6L>4nxv7&5J&`kSr|g2+<|V?( z$@4kTTCQAT8Tt75NY+A_%-v6`s+Mhke~3)Ozom~KjwtOYu=Au%AD2+>x~SSSdfx?* z(9ypnb!92sa<1!-+VMIbQx*`53hasg$6`q61 z$;mpY?Im2Yed=|T&!*{v7fl*Jd3kqJiC8;1+J zEH7t6ZAM00VQIucTN$GI#sOXtjW}eso5_(E1uYI0yn@018IX-|!BaVn4>0*`4#{=% z7$8Dp^DQACfs8=co{_WTGO#D(&vfg$$v{P~IKP|{I6_tYlB$YEi7%zEBvUPE9cQ^6 zV*DhdBm*E@`}ojXA{2GcQug0v8EW$lr_RJHXOY-L1wo6KQXu_crazq4PV-J+b7Y9; zZ)%8~ygL+5aW7hW1e|^GSt%!;Wn#mOmcNmbN>N8j;3qywJAbuctDx%c?k=>eYHW-x z9I>~6Tlu%Nw46}%1(2`HB5H>#<5(JTaTQ&3Sgl@mD_MSNZ?zuoB178Y2)RtuWnOcA z(22%LHI$bib80jb94wyC(#9RS4k%i|oR)pK{2egl%Z=2(5=!K-)4Q^^8*w#D+Wj9p z&BSmi9DwbtwB>!<1Jux~SHj6iRNyu?(D0p>U^|~=#RfDXWHsVoTaMtVGzQL$#^5AC z1RD@;9#1&Hlk5NDm-%y4)X4ky@B8xFBy-s=reAgZ-YrgeDw9)kJ8{%c3!DFo9JU7y zj%4_0eKDIcz%K_PevgVP$Whmg?JJL?(*BqsQ1LsQ;D!0R^L|Oc9WXNbjQ~8uuHG=Y zn>?;+B!#j6w7N!|%HS$6*nHzZ5i@U653vDXL*gN#A~P`r55@x~1r`q%7(CA76G3+` z1yHMpgAi!o$q=8%L4+k_{4o#?e+~1m+2LiK+@ZTY_oU4paowFF@v@5ofW}mpGLCVx zB7Xm>&rq)Ig^d^^+v$Kn+^x!})hDr%j#E96zM|bg;MP?v?y~^?KlAhx{oJcZ8$@k} RROpWZaCUTcC_Biw{2%iozl{I@ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..8c79adacf6c49107760555c944cff3f4accc3c21 GIT binary patch literal 2161 zcmb`JdpOg39LImV#8NRwsno+tH>af21xKdQMRF+0B^l~agv}ZoZ9A0ig*uTk-A;+J zCY{(BWlnKcB&XyIi_O`wskKYkW@g*@p`J(mQBTh~zvuZ~zQ4~O-{1H1e!t$|-|;Q} z-c!tM%m4tG;}cKRlM$kv z0>5r{rD1&)`uAH%;q}RF%A*n8 zp^s5uQD}^--y77hUr+iov4$!r{Z`lWTOkxSzFk5qXS$zkPSO!ZX(d8vC6~+hS{zsr zjjdP@b}_;fk;P^^^fa&W)oLYyVQlylBXdIQ)6^p`r~r$f_*&OhAA2l`xU~V#$?MYy zEG&n6@`s+k)-^+x#GHROkmC{>&<|l0N!=7CZEJZc6-Sqql-nAK^x~~NDp1@Kmv@a* zzpHzH9mSPpXZ<7E(A?BQcd0;59FOv~dEF`;ww^JjGxcGD zb154B>~u=2HH7+bADi)EFx~-uUa=+w8Vx<@TrK~F5v*dGakW+$69R>@`o%eE__XZF z{s+F=DxWu4C&F%Kdv zDj&4o%2f@?qWF|{53pvZa-Co@-oUk1_u3OcIbf-;I_xtR_R8f z&f^l1*slzTH%dyh)4lL4K4@alXB9`GnY!g3n;rY<^gzR)s9)xS&#pFJRb@?OX@R~9 zrnG>@@){9D+4)C-fOwuxY#9;c<~5Gf66=$m0-W>p;79j!%EcOEQ=u7cnn{1 zjYCtfxfgt?rO6cetkZYLvgkqccH{SbzQ{2QmGGwBzjmw@0=6tQLO3yOX@14gT8KMN zX894PPKS1H;qiExxRDO+O&KnIc*YZ$`BEm6A$FkULEVqLr@j|@yqy1u_3#-N1F#Vh z=Q?qkzsnS3$Pm_D_FHgvUrI_!F75dONc6VqP9_NK^mP3gG!r(~&nM|%Yim_)LZkan0LWeAAgG!|*y&!$g0IAv$WM4~-pe(wRZ(+BI z&DK(Nyhv$Q(JGpNXY1wA1Ba%XRWbXS2Nxz|Lnw&NHX=g2R?)~Sun;D@B^rA1<)Lp? zq_TOA#;6%00%cf3qt;1!6@sude-}H9Ys^EQGY1YCvyJI8dK2iE4WW~Y*8`?^!k5N> z7>u|1ePlrZHZoG7Qenvf!KEVXwCn`v;}RSmZ!yReg-W zo&;w)=rQivz3iEnK+_a{LYaGk z+kHP|y{7;G literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nocaps.png new file mode 100644 index 0000000000000000000000000000000000000000..235d1da545d2875cbb44391ba69ecc3e8ccf85d6 GIT binary patch literal 1888 zcmbtVdo+}39De7b8JaTaqINRFBuo_PBFW5>opEc+UAhdZbYp~-+-4*x)}`EHq_eGr zYT8_;LcY|dTuQZT%3#orOU0O>VG#R~c8@>yoZYkUIp6z!@Av-Bd4J#YJiq66ZpU`| z0+r<|0DuLyjBQQ;AZYOWAa))+2O3o!g%@;?m8}aF9uZjIczCbOVt57tFmKiLgUHh` zCj)>%+HU*SE0g%6-@$tHBnjr%1qx>I^>M*SAYa1P%CCLcFj&#YV)9+s>Mae&UhK~+< zd);(P@DJq+%bHudg#C?CLWwTFLfGFT0VyNu~1$F7^ybM$dKE-DG_;|8VEh3%~EWpvG1j+GF!N~pRcoi+hfVD zii4m^90y@eA=0dwnisW=X>>8lZz2AP??U{Qt1`rT40iuI47O>f0%W6NO)jC(_p`6n z=edXj;+PBZ-BQV@(N^6F0J0NWz#(PF*Dxg=OywAL^EU z;#D7*WzWc1$7T^%ySM{laZgJ>gmVsB5L*LYvSTAoJw zY;=z;S9Wr2_`SfskY&VD0C|dq!DW5L!``hm?&m-h{Z%G#QYJ|W(jIl1UrlCRK5JG+ zBD1Yk6WX;O1ibL*Sbua%H1~43eDbsSdmps3HET)4d;%i2TK6m|-IX~h6$ZFh$L&-m ziSU*eG?DwrA=~vss-KbB0!FTeU0Z5=ND0}JuJKh+SJ3V>{Y&vRiN78zqa zrt?*smd&)lOgzzu-s=&-CM6@^OL+xjAf{pGn_ zZtGYiolXyJ(HhoaqsE8(Ugx&+_ws}XOEwP{a8BVk91n3a*fYQ3__8sP>Mf-kS=3y2Ece);CQS*r}7B}q$%aM<= zW^Dtm=2b!L9Z#jwKuI!G1Cvc4*@sO|gh`U|zY<9Q;3v~{{g*Gy#HgX4_^P3=kiWPn zoaasWjvGi+u!|+zjpOa$2D{W8zr7(t1Z#E3ELQxeYo?lV0bUouSXt0CFAjmZp#qjU zHqjCAI)m|=Il$`fAE`;R^Gp(Dknt+Pmy2M~P3|qx{F=qTxB0;>*A7zxYg_W)N(>9f ztK2Qw6>V$Ah}iF3bStUyNiZcI>Uq)sd$IcfZUO2o^+Umk>6fk&{22{$(}+k^gUwe9 g`yV|8?nIxKqSTP3PE&S^;1&sNZMJW_WyOs76TvbSx&QzG literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_custompoint.png new file mode 100644 index 0000000000000000000000000000000000000000..74ec31b2e25ea48a57d3071894f571ba42f50133 GIT binary patch literal 2159 zcmb7`2~ZPh7RSFFkig=EprhR3Qg(2_Al67Yk^l+>Mo_sF9SB|wghLLwkA%UHVOYiG zN`Vk&ad8$@6qox*xMYykz$gNSTS6F)AP|T_gl%xu+O6HHwX44F_f>aQzkcuc{{Q~* zxQoLMMRi30zz&Mzk&^(x$WZ(mEf1Z;jGJiC1sP#WaYsW(0{Q|A+ACakJRJdm{JyOP zfqMh~9?i_!0QR9kUu#(#yiqI*sj819DtQ9oMwPZ)VdSrr< z?7r!uApdILv3z8!r`y%N>@Zz?fLRbW$H~v$GiBGIrJ?ww`cV-q%k)xh^f|tR^k&57 z5W&*Yk_H$Xz40gzhb8Oe0{8=@GHWlL{51@DPC-Z688A-4(d{aqaVrNi7FZQn-d83K z0haE<_{7_55e%GFOpkb^Nw6xFIt+{BnnLbY_0|Xq`129h>xNh2hJ6}iOlPVOtohd% z`#yX3Rw_96KsB+Ufa4++4{Ia}tYt$)DSK);Hg7&88#6X#*fH>1Gg^Zfom<3dXjQIV z*VN%1e~iKhyU3q(H=i-V@qFy4)Kg3z(8|FS>*Bx7(#4Yb>yHX1Wurqg=j@E%u-?ry?_nMt9(O7@Z88HwSZY@H2AEfJ#NB zo*VyGYbecS;FoW76LpG62XZN~cOtHgwdr$-b0&cYoVCoOG5r>vh5rA*+X`wnNH z-_8~Fl~si{$4b|_514J@vN6=_G{&*?tg+i1@3+D{X+`How9mNr*b;2c&+Mz_SY!IP zy`8w4)n11MUqd4tPrDypu#2B%E?gY+&*N%4U!^g+QTNj(DT_zCF!l@BW+&upMT#%n zaWB0+qMhRmepc``%4>yfv2_vs4~|vxhM4P{*cVM2xLZHsZ z@U*3sm1cN&k6`Jml z{MrltD+i6EOd!}6hS_U%<9~SrN}nqHH7TU_*VvCdmnVS!*(*Pu@kcZ~^uFtqP|=N2 zrv6dr#gIy+&O;U&1BFJTzb`q^t&0>sDzGSutMPLVGJWiXGH00vy zT9Y&yUKg}1OPH0;&(BxgcIAY;YyOj>9aJ^sJ&{BbIqR(I%Z(lOrp;$1CswYkhNwIV ze2nT*W(N;XI!@PYZoWv`80CL=Ej2DqvQqNGn>I?6MM;7+;B6oz+(6Qib>QWk_>OT= zQ1VWeAkH9ieK;(Bs$XqtbGR< z-5QWg(_3l9X^Iha;T{{nJV~TKMWhCl;kU5Nm|ZF87kQz0&vQ74IiZ~iw*E<+j~Y0? zKN&ed!%p`0Uw?W&vsIxbqf5{6ge_2ejA0n3WEjd)sgJz;u(uLGyyF)(GYTyiRCxPu zF?E7_(}LhJa=P^2_jAz`C(YUW%HI#H2bEo_Sk4n7(D+>t{io$Fh39s=3fR(1-uVnwq(C>#w2fkY>c|y;oD@Dfoy)M+!DvA7VyjS3or zQ5ZJa(8K~G)tn5TxgLDlQ2(;p?H2HVi|zm5b^%q(<@!#%803lFWiELnxWpZ*q8*if z4SK!XI5po$>4$CzIn~mx_beYmG6N1G<)?p108b!&@n9pqPS!g#_gio82}=T@PQr)A zuz)Yh+ZYV`hL)C=W+ztH%ElBE$WfH`8Aui(F$DP^f!Nlm2s-}Q6o6Sk9^oki-bhA3 zB^ti<+=uXALgzuGdouj;@4yj}5Ro53gO8)RWi>)v`lJ=lFUCT>$-^9TKLMyQSuMt@ za7zrN+p%<@^~sXbedVGo(39zyVwTOlH^3IIU)ZgngJg%d6H|{WZ>3VdTeb9WtyS9? zWkWz1LpDHTY$y78K38!cl8T`RQ9+R3Jm)t-7BIVFv33Am`ioOcH|b09yr5=&@S(q0 zX~)jW*cYw~{u|rII$Mtdh1obbLh0z|yXdD6{p7tXs4jjHUgFA8h5i@-<*3V%d$!b+ Fe*#OkpHKh* literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withmean_line.png new file mode 100644 index 0000000000000000000000000000000000000000..73520f0118d7b5a6ba015e585d3649be3a926e99 GIT binary patch literal 2096 zcmbVNYgAKL7QP`Da-o3)x{y@S1W=h~G?P`Csvt=~G68KvM;=9SBv>6RAOVBrVFF2Y zcqmJBumXb^S{$gPAu<)D2qZidHGxpH1`-~k4ImGJP#%E*b5c9DotYnP?^^eseb3oz zpL6#9zHev0$7b2uthIq4$Tq;=ZyyAq7{H!FtiU&6YeNheV4iP4FajPvawH4_D-(V6nD8iK+n|?ze|6W1lix+4-{6>vsnN6kZo7ptl zyts7ciOFQDN5P9F^RGiAOh^=FfwVd>NS7gtDj1)&NyylOf)87h@Oyv7QV31hLcJMD zNTu6yax3a(#9%pP>S1}Hu9TIlJzQ1rUBdRFcVndQuGBW9^c|YNlzY1;Rx5d5wplPP zSZlln0!V@)m40@;M)BM>X#%M8p5z ztXrFLP8O`EW1E~wInM^Bi&FP=IFlGGeJ&(QIOq-iKA!rRX@m~IqUrCjVkTaA{sE@b z6;b6e@xOBwc==ZtctUDTzs_f*&7OppnCweJICuZ*x|G9ITbJ~wDAhlUEX z+!#-buhz^hO*%!Cmp6!QgHFKy^?JiweC5%B;pUt0h2;BUq@j5Jol||`7PY$<={X+R z>FSnF?fgJQ?}|aGqMtUP#uF$v0umfdhP^TJmDH3|qv^wr9hMZLVna>dQ=QJ@p0mJ| z5JOB$PVxG)5t`fK)O{`aYU`W1YPx)7E|`4fwSzRF_r}1p?t2=ksku`ju0pSk4c4Sj zAP2R5It6uRGynP(8_f5}a-ptH8T5D3<%<=QGaNaa&DK6YAD9{@(@acFO=%~$<|{u+ zO!OEE8ruUIb;^9{^1?H{Y?#pW2yfExBBY^aWHd(wWkL>2q}J^7ja%c+4a-e(Ytkx- zzcCxf8NKq}=vap2DZ0SZ{nBFHAbIIkrbCTT+}lMC#A4fVH5I$5U!zAO)XN5m;@q9j zT3J!(r69fcUsjMCZ6Slg={GFUiNWAvl{T0hSu?fPuZ;m4I+F~3V&={6uy8!Iw#Ndv>9V)Y;81Ke#p3|ol` zfeKY-m%Xp$5Agiyos2(yvNjEzVBM!?CWI8Aw-6b^s!O;Mfsh*LIP?;HKm^D{#yJw0 zS>qFXy0GezZBu-Z9$lNuyzmI$jeqATn16tYC!T?7*~Xn900Q97hs?+h2?Mx!88T>) zbCvvsMLv$C2m@;3ScSW=Wo$O;_ycJH7#?~PPHhsZItB;TprosL-u=4ysu@IW@d%rHqVc7)Nu}V8AnPQ&Vp9LjnIn^h$U_=pO-h&6hI9yEMLXRarA#F zRcI?YzCt657(LC36iz5kp>SZsvPRGSpgMeml98$=wb&3%9i_68{#D2jQ)g8rGVug zn0vJBu=PU_$`f1*NY$%?gWz`fq5Ed7`?2{3aDj89wLAG6!XI{v;` zcMHJx%O1Z~%O>X#Ypzk!+;D2XvLSi8*?o6QPONP9;nF|oxDK^ieVc|BvVm%&?z;Y_ eZumc^TYT-=iyG_=`C}Al`A`6p?N{l``S{<2HoX zgNO)nln#m<${>ewg+tIm11<_ggn*G_U?O)Qgkw57?vz_QRXa6bRd>Jc@9VE#{rmrZ zuP?efJ7Ux~ssjLk*-PJZ000m)IG%)5;dAJg+5q?hhVS0%4#9&3`CWkT)k5e;!T~_l zSa~3F)_-mY01#>K9y|A_ENLG#K&M-;bx<;Xi56#fGwHdt^Ta+Q1!JaE-C$`HtRwM0C z`)%P70K~3zDiFY|0gq;e0FSGY@cV(-!$_!k9gTDaK$e2|3r2rn%9JP=3KLyv`*Pwu zBG5Ihd#-kDV(rq~C#kZ@!KR9`GRv`0MxjSQaT4cs|IXm{n5UAGLxr-jmxXS8_H%Bv zt~_3k%6W~&V!xoile^q<zS>BY)9w^ryny_VcmbfBhRd~~T{VW+{?CxcXdAla zQ}Cl0-JnB4OTv~~vcI!LK^<;#OBz#agNr~tD>1^2!qc>4?%36Ik^tu3m`2=(kez-6 zFo7<{es^aHey@1`Yy4uUD%5PsRrS}nnkT~k+cC~#U>b35E98?uZO<%(vpE!t`1})j zi{PDb`22!KieMT6YVDH)uTW5@ACW|J{P-nyhugn!f2V3+GxS5bn4?%yAj4-8o3%+( z$$T$`V#1|9nIA*l*zy*g5>+M|nS6GFOf0Ev3;p>jeQfSDWam3-dMJ z6|y#q9Lma1x1O$|hE#eap3KxYn-(Peu9d~{nbv=<+TIP>5+Vm=AruqNN-6Mn2QjDj z)pjv!WrrL5v`Vw*W>j{w37^QRT9lN!S7}zklGC$4bGf|KGP%)r0H#C< z7sAy@R)hP84(xdECv>d5u`NAkMKROPCZaMUqM~||?>^{kI$r7@6BEPW1X*X6FE20a z6FnKo$JAqWfzoat>MAFJ;Dl+Y-zAf?`jzHjSw%o3w{yHpU<%iJrzG=@i0Ez1W7#dx z*AWFTZ1P@=U^YBLkt3Y{Iv3sKAVYlgF)Xxq@gbvA*D-;@um@&S56y`*UfyM&HT@!y zN$7YWGyG0Xep?Pg7RA`XjQZdjCO;_|Nkn&{4K3j3Ab&Zv%e;(D$`!j@*Z;x=#IEUlhM_6jIlL%HmZ3E*GMd8Nxz0K>Ph8(V5<&7I6PYYpnjImWf@f_dPWk|&zn=+_ohZ_bQ!@7;n z`|7l&Q?ifFqc#_^fbCmRRl*ei^>@%0&#mT9@4i=E-J6_j&NvU z+f5(b6&Mth2Dt{m-%el6j;b*XwE3tZ$(KLcH6v24PnPsFry)YRp#h@;{o5g50*S>e zp!FE|1RINKNW9f(K!+Wp52oa@fGyu7ngrXEm)*5I(r`>-v_8EL?_`ar*|ZI`22<&P z1p)ckjbnI!{~+_4X%R9VM-sUHi0UvUI~|NG$X8ZI0!*YLq@~TJNAp2XW6I1m#r#OJ z^*$gYtF~6$+t9$jp&LwDtu43ipDHn28edPldf~++H%XcAq%4czdL)j`8&Y{JwK3#z z85CY-4{Fp28Y{Smy9W!?kbw`;Q2aOjG)mR8#^%PmD;Mh?9e*y9$+}kRq*AGuF76CL zpcbQ$NsH@(DLIvA&z{9lH{Nc=YhCbIL3fl3irP9>PlaqDqXiu~nWjYAgc@>tc(;&s zbS*kC(Na9;08W-H`++S40*$I2TiBzj@_#e;cWhiw91sefFM6F+$vil;?I@f@bz_6o zuNdstz`}qb!Kgt`>pULh(|iv9QSE5fbg*IVc9#~2R)V5sRZmp zO|ASV<#1d0ZI4fo{QJ$Y`>=Vs(YwR8*6p_Y$7U$7jS63yE?PkOxA#T0)&K2vb>zgW zOB*a+Yi~4uyHv|LQ$NyPTLzC3K9HLVcK}`<^e3C_uZgapW*Dh;TYt7Tf`1@@y$;TM JN_I29`31=~tabnZ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_withnotch.png new file mode 100644 index 0000000000000000000000000000000000000000..c95f103856ff11a852d1517d8930abb487cda942 GIT binary patch literal 5119 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 A9RL6T literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 835d701d428b..a8f18f57db74 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1037,6 +1037,132 @@ def bump(a): plt.subplot(2, 2, 4) plt.stackplot(list(xrange(100)), d.T, baseline='weighted_wiggle') +@image_comparison(baseline_images=['bxp_baseline'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_baseline(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats) + +@image_comparison(baseline_images=['bxp_horizontal'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_baseline(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_xscale('log') + ax.bxp(logstats, vert=False) + +@image_comparison(baseline_images=['bxp_customoutlier'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_customoutlier(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + flierprops = dict(linestyle='none', marker='d', markerfacecolor='g') + ax.bxp(logstats, flierprops=flierprops) + +@image_comparison(baseline_images=['bxp_withnotch'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_shownotches(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, shownotches=True) + +@image_comparison(baseline_images=['bxp_nocaps'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_nocaps(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, showcaps=False) + +@image_comparison(baseline_images=['bxp_withmean_point'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_showmean(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, showmeans=True, meanline=False) + +@image_comparison(baseline_images=['bxp_withmean_custompoint'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_showcustommean(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + meanprops = dict(linestyle='none', marker='d', markerfacecolor='green') + ax.bxp(logstats, showmeans=True, meanprops=meanprops) + +@image_comparison(baseline_images=['bxp_withmean_line'], + remove_text=True, extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_showmeanasline(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats, showmeans=True, meanline=True) + +def test_bxp_bad_widths(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + assert_raises(ValueError, ax.bxp, logstats, widths=[1]) + +def test_bxp_bad_positions(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)) + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + assert_raises(ValueError, ax.bxp, logstats, positions=[2,3]) + @image_comparison(baseline_images=['boxplot']) def test_boxplot(): diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 1aba8e02eacb..624a00cc943a 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -6,8 +6,9 @@ from datetime import datetime import numpy as np -from numpy.testing.utils import assert_array_equal -from nose.tools import assert_equal, raises +from numpy.testing.utils import assert_array_equal, assert_approx_equal, \ + assert_array_almost_equal +from nose.tools import assert_equal, raises, assert_true, assert_list_equal import matplotlib.cbook as cbook import matplotlib.colors as mcolors @@ -92,3 +93,94 @@ def test_allequal(): assert(cbook.allequal([])) assert(cbook.allequal(('a', 'a'))) assert(not cbook.allequal(('a', 'b'))) + + +class Test_boxplot_stats: + def setup(self): + np.random.seed(937) + self.nrows = 37 + self.ncols = 4 + self.data = x = np.random.lognormal(size=(self.nrows, self.ncols), + mean=1.5, sigma=1.75) + self.known_keys = sorted([ + 'mean', 'med', 'q1', 'q3', 'iqr', + 'cilo', 'cihi', 'whislo', 'whishi', + 'outliers' + ]) + self.std_results = cbook.boxplot_stats(self.data) + + self.known_nonbootstrapped_res = { + 'cihi': 6.8161283264444847, + 'cilo': -0.1489815330368689, + 'iqr': 13.492709959447094, + 'mean': 13.00447442387868, + 'med': 3.3335733967038079, + 'outliers': np.array([ + 92.55467075, 87.03819018, 42.23204914, 39.29390996 + ]), + 'q1': 1.3597529879465153, + 'q3': 14.85246294739361, + 'whishi': 27.899688243699629, + 'whislo': 0.042143774965502923 + } + + self.known_bootstrapped_ci = { + 'cihi': 8.939577523357828, + 'cilo': 1.8692703958676578, + } + + self.known_whis3_res = { + 'whishi': 42.232049135969874, + 'whislo': 0.042143774965502923, + 'outliers': np.array([92.55467075, 87.03819018]), + } + + def test_form_main_list(self): + assert_true(isinstance(self.std_results, list)) + + def test_form_each_dict(self): + for res in self.std_results: + assert_true(isinstance(res, dict)) + + def test_form_dict_keys(self): + for res in self.std_results: + keys = sorted(list(res.keys())) + assert_list_equal(keys, self.known_keys) + #for key in keys: + # assert_true(key in self.known_keys) + + def test_results_baseline(self): + res = self.std_results[0] + for key in list(self.known_nonbootstrapped_res.keys()): + if key != 'outliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_nonbootstrapped_res[key] + ) + + def test_results_bootstrapped(self): + results = cbook.boxplot_stats(self.data, bootstrap=10000) + res = results[0] + for key in list(self.known_bootstrapped_ci.keys()): + assert_approx_equal( + res[key], + self.known_bootstrapped_ci[key] + ) + + def test_results_whiskers(self): + results = cbook.boxplot_stats(self.data, whis=3) + res = results[0] + for key in list(self.known_whis3_res.keys()): + if key != 'outliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_whis3_res[key] + ) From 91f88a277100c9435d1aab3aeb27d2c922b99ade Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 14:04:24 -0800 Subject: [PATCH 18/43] BUG: fixed cbook tests for Python 2.6 --- lib/matplotlib/tests/test_cbook.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 624a00cc943a..efd53c03075e 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -6,9 +6,9 @@ from datetime import datetime import numpy as np -from numpy.testing.utils import assert_array_equal, assert_approx_equal, \ - assert_array_almost_equal -from nose.tools import assert_equal, raises, assert_true, assert_list_equal +from numpy.testing.utils import (assert_array_equal, assert_approx_equal, + assert_array_almost_equal) +from nose.tools import assert_equal, raises, assert_true import matplotlib.cbook as cbook import matplotlib.colors as mcolors @@ -100,7 +100,7 @@ def setup(self): np.random.seed(937) self.nrows = 37 self.ncols = 4 - self.data = x = np.random.lognormal(size=(self.nrows, self.ncols), + self.data = np.random.lognormal(size=(self.nrows, self.ncols), mean=1.5, sigma=1.75) self.known_keys = sorted([ 'mean', 'med', 'q1', 'q3', 'iqr', @@ -145,9 +145,8 @@ def test_form_each_dict(self): def test_form_dict_keys(self): for res in self.std_results: keys = sorted(list(res.keys())) - assert_list_equal(keys, self.known_keys) - #for key in keys: - # assert_true(key in self.known_keys) + for key in keys: + assert_true(key in self.known_keys) def test_results_baseline(self): res = self.std_results[0] From 65a686cfd9360cace5ad763a0e140c5bf4ce4e78 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 18:47:26 -0800 Subject: [PATCH 19/43] ENH: allow for custom patch artist and labels to the boxplots --- lib/matplotlib/axes/_axes.py | 48 ++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index ce4d4bb933b7..354f02303c32 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2933,9 +2933,33 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, # lists of artists to be output whiskers, caps, boxes, medians, means, fliers = [], [], [], [], [], [] + # empty list of xticklabels + datalabels = [] + + # translates between line2D and patch linestyles + linestyle_map = { + 'solid': '-', + 'dashed': '--', + 'dashdot': '-.', + 'dotted': ':' + } + # plotting properties if boxprops is None: - boxprops = dict(linestyle='-', color='black') + if patch_artist: + boxprops = dict(linestyle='solid', edgecolor='black', + facecolor='white') + else: + boxprops = dict(linestyle='-', color='black') + + if patch_artist: + otherprops = dict( + linestyle=linestyle_map[boxprops['linestyle']], + color=boxprops['edgecolor'] + ) + else: + otherprops = dict(linestyle=boxprops['linestyle'], + color=boxprops['color']) if flierprops is None: flierprops = dict(linestyle='none', marker='+', @@ -3010,6 +3034,9 @@ def dopatch(xs, ys, **kwargs): holdStatus = self._hold for pos, width, stats in zip(positions, widths, bxpstats): + # try to find a new label + datalabels.append(stats.get('label', pos)) + # outliers coords flier_x = np.ones(len(stats['outliers'])) * pos flier_y = stats['outliers'] @@ -3061,19 +3088,19 @@ def dopatch(xs, ys, **kwargs): # draw the whiskers whiskers.extend(doplot( - whisker_x, whiskerlo_y, **boxprops + whisker_x, whiskerlo_y, **otherprops )) whiskers.extend(doplot( - whisker_x, whiskerhi_y, **boxprops + whisker_x, whiskerhi_y, **otherprops )) # maybe draw the caps: if showcaps: caps.extend(doplot( - cap_x, cap_lo, **boxprops + cap_x, cap_lo, **otherprops )) caps.extend(doplot( - cap_x, cap_hi, **boxprops + cap_x, cap_hi, **otherprops )) # draw the medians @@ -3101,19 +3128,24 @@ def dopatch(xs, ys, **kwargs): # fix our axes/ticks up a little if vert: - setticks, setlim = self.set_xticks, self.set_xlim + setticks = self.set_xticks + setlim = self.set_xlim + setlabels = self.set_xticklabels else: - setticks, setlim = self.set_yticks, self.set_ylim + setticks = self.set_yticks + setlim = self.set_ylim + setlabels = self.set_yticklabels newlimits = min(positions) - 0.5, max(positions) + 0.5 setlim(newlimits) setticks(positions) + setlabels(datalabels) # reset hold status self.hold(holdStatus) return dict(whiskers=whiskers, caps=caps, boxes=boxes, - medians=medians, fliers=fliers) + medians=medians, fliers=fliers, means=means) @docstring.dedent_interpd def scatter(self, x, y, s=20, c='b', marker='o', cmap=None, norm=None, From e9c1c9b72f12855daceece5c956162ab9e4d7d7b Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 16:51:17 -0800 Subject: [PATCH 20/43] WIP: more tests and add option to toggle box TST: added tests for all of bxp and more boxplot tests TST: fixed boxplot baseline image TST: added baseline images for bxp --- lib/matplotlib/axes/_axes.py | 25 +-- .../test_axes/boxplot_with_CIarray.png | Bin 1870 -> 2492 bytes .../test_axes/bxp_custombox.png | Bin 0 -> 3212 bytes .../test_axes/bxp_custommedian.png | Bin 0 -> 2032 bytes .../test_axes/bxp_custompatchartist.png | Bin 0 -> 3964 bytes .../test_axes/bxp_custompositions.png | Bin 0 -> 2044 bytes .../test_axes/bxp_customwidths.png | Bin 0 -> 2238 bytes .../baseline_images/test_axes/bxp_nobox.png | Bin 0 -> 1859 bytes .../test_axes/bxp_patchartist.png | Bin 0 -> 1919 bytes .../test_axes/bxp_scalarwidth.png | Bin 0 -> 2049 bytes .../test_axes/bxp_with_xlabels.png | Bin 0 -> 3477 bytes .../test_axes/bxp_with_ylabels.png | Bin 0 -> 3252 bytes lib/matplotlib/tests/test_axes.py | 183 ++++++++++++++++-- 13 files changed, 182 insertions(+), 26 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custommedian.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custompatchartist.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_customwidths.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_patchartist.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_scalarwidth.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 354f02303c32..52da6982516f 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2927,7 +2927,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, def bxp(self, bxpstats, positions=None, widths=None, vert=True, patch_artist=False, shownotches=False, showmeans=False, - showcaps=True, boxprops=None, flierprops=None, + showcaps=True, showbox=True, boxprops=None, flierprops=None, medianprops=None, meanprops=None, meanline=False): # lists of artists to be output @@ -3025,7 +3025,9 @@ def dopatch(xs, ys, **kwargs): if widths is None: distance = max(positions) - min(positions) widths = [min(0.15 * max(distance, 1.0), 0.5)] * N - elif len(widths) != len(bxpstats): + elif np.isscalar(widths): + widths = [widths] * N + elif len(widths) != N: raise ValueError(datashape_message.format("widths")) if not self._hold: @@ -3076,15 +3078,16 @@ def dopatch(xs, ys, **kwargs): stats['q1']] med_x = [box_left, box_right] - # draw the box: - if patch_artist: - boxes.extend(dopatch( - box_x, box_y, **boxprops - )) - else: - boxes.extend(doplot( - box_x, box_y, **boxprops - )) + # maybe draw the box: + if showbox: + if patch_artist: + boxes.extend(dopatch( + box_x, box_y, **boxprops + )) + else: + boxes.extend(doplot( + box_x, box_y, **boxprops + )) # draw the whiskers whiskers.extend(doplot( diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_with_CIarray.png index 614c60487d300e706ce3fdada6da1163c31f2fa0..9fb197604e80b9a7c61dab53444d72dc89ee8f8c 100644 GIT binary patch literal 2492 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 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custombox.png new file mode 100644 index 0000000000000000000000000000000000000000..6329a2c51a712e2d69c961cbf4d85e284cd23398 GIT binary patch literal 3212 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

0RN5-Ph;C=6?U&8QW<_+R+(AKHIapHw{N zE6>bGCm*j*c43>q`xuA3ERqe^g;07Yg%8&KdomQOZMeSZv0_YZA+2E^?12A8_!NnS zm%3cfRd}GW5MIJntB=&ZmI(O0D=X%wL=u($JI9LMh>zr$S4z*1S29%*VdA-!R-Wi2 zIPI-?RhfIBb&r^GWpZ#CJ2 zdHFeg&b)OK9E~HAnRkHdgd1d!tv{!G#OwCqso4-xVOkUi03uZYNVn?$d59{L+%nxV z{g}-VA97Jlt)6AP-hu($HILf02HEd9)|L0+ii3$t%&9t4hqZY=?1ViCAK^@Ru$ zg)Yqh27!`_7a&+ksS0%>3GnnANq}!+9pDD9mMq!#H4#oh`wnx__(X~P_ z_N^HJ(k2Z-pnv*n$V*z{I{JN(dcA$jSl$9nXz9xxge-D$v^9v^F0xJw9^*21msr9P zQCYf*!egbUTGy{WMxeW$;#7ZS@|=z5_dp_{%R5h`e5QqGX1A<<;eVo|%2zj$F#vHy z%+_g-Xz-~1ZV5@6U2MK}liYFe@9x~stDj&<_BYmzH%i2B3xODx(7TDVJ~^L>G9F6y zW~j{>XVnq0J*EIk<5-8RCQW`+ZADcDurFLv0i2~t>DC=}$pGy3xKl?>C>TR9Jkq2l z`tr;=%|UOYI;6j)Qk=6rDANBTC@oW4v$I>c^MQE$6N8aM2GsUQpXP{&Al`hfc&AWf ztQ{G6MV|TehDi+Ebn!Y-uYp(uDlSMZ4WT@|+#24xcNqmL3Z%2m{&;+yXH-XD-%;aS zJpNCj+UJtU@G9v28Dz~bFSadW2H|18p@jwu8EG7j*4%c(r!~F)Lu!_xL%MQyzjotS`wlSN^w$U zjJwQjR!T(TVaP8I;&F_V*yW}-T2iEYuV2l*9|SkxC>iNpVg7DEeZI2-M*bfAKK958 zPWxfZvbW<3aOv=_;Iy}AHqaHqv;cV1`yp)Wn=8$Vf9;*92{mL$UJF>LI%9EG*34;q z+&KY#WCf+AvI0I7N+F58mBM6jm228^W<$-E{Blfe)(}4Lzj47{)Uj~a4+bxFZhL${ zZeFT*=Y_ygZ6&LFw+OjE4=YFnQTq*7S_=H0ybl=E8;ho!Zg#7E3^+Xd<)UxPmGwV( zZ`Y$}{F%+!)vox^`IeM}a0z8nzH_-HrQLgZMw$1L(jOE>_Tc; znPl<4#B;|5>U1+c`zm+D9(<}FK-2aYujlVBFJiaTqIuV|M~xa56-INtCNt8q2Y(IW z4D@_$FW}vv#g4Qt{SlFu%6*4dpL_Z&{>RtNbz8FZX+}c);niY7u9;cG;&inlKaVL? z&{4FdG+SP$i|;zc_8wA!Yt6|sXVQT;G7uMyNy0K?|E_I%(SaH@m?W>B0!yeqsqqD7dRwVPiAHGpX6g$f=_yjNW|uBU`(6=yKBrc- z>DTE)RIvUVn^RO?5J)VLnv?ygem&4PimZWG9m1fDdlB9gG>9dIe*%v>u(mQdHzKw!oI_!%>mLmpOrYUY2hYe z&(%DnX2NknnqCn|Am|hg@YSbI3RhVgw1FWxuy%b61#n8bJo0xWJvbDu?twe6c|^j@cXBMRaB}-VHRmTS=FK zmQ(>0bK4Hq<#gpZmJ1%w^SYlW35!Bc(v(NXvdoShp)xLv-P;^3Dv%?yzboSsnzrB4 ztY;gWzYRRB5XG_3+ya+;*6&&!5T%ZI8$g({$(?V=Uej|^1nq+1Bj>*xe7sACI;@WhM|kUc9r<3R2!BEiZk#Q3G+@a!cbXaHBRR^YKPY#L-IBm4x9B>KU_nyASY#z5bvXY_g4Xr1!yTKB; z<&^U`DZgiuKg0aRkEr;JQ;QDfiW>b*^RcKfDR3KgYc@*0MU+&t2{%Zv~zH)`fv$5 zg0FKdVyjHI;$;zNx>8|){Z^4j*G-g($Uj)@c6Ir$PHFmg2H!+Vc!E5~9BmkFZf>Ye z!fAU`MPY^h;Okno;`?X&x=|`412S2F}QR0=pq%4M&V; zM4yHTmmBGSTz#23;Y<$)Eg&0Tl$H}mD9_7yOF)7X2tCasgI$ybVtq#yasApEA+3cj zK}#My&bV_&)ZtW~T#MZa;IlmA2D;+(*(I>E$WKOsDPx~NI)|wo{vrrof_KK{UbPtD zvdZClPftY3Qvp{xlyQ`=PZc1p*aoN|09sQRy{#N}pFlp=&?AC9HXY}~xZ~Kch?{Eo z)}a*|DqzBt7z*rz!ew2TFH32mbLSa!7mU%6dbObAl*5IbSQuRhkCri_N)Z7SDqfvC?y7t(9p=4o|N%7 zT9ic=lCs~%Y+WKi_|as@0sCvwa6lM jp#A_13Ap{g$EL^(bZX$hL+eKH`V81$>?~_7e6Icr@98rA literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_custompositions.png new file mode 100644 index 0000000000000000000000000000000000000000..35a0de4fa8ee2ff45e8f713857777304c480cbc5 GIT binary patch literal 2044 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_nobox.png new file mode 100644 index 0000000000000000000000000000000000000000..b12088d71ee7206d3db94fec22ba8c1cfb8746db GIT binary patch literal 1859 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_xlabels.png new file mode 100644 index 0000000000000000000000000000000000000000..fda6da5059fde07d871b6483136ad9afc415831f GIT binary patch literal 3477 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_with_ylabels.png new file mode 100644 index 0000000000000000000000000000000000000000..07867fa6ddbc9f5a3e38de678f6d0aada8c5b68f GIT binary patch literal 3252 zcmcgvdpwi-AAdG0l3OVp&8D2_r^{4~TqCzt$em)S(*?QB+(z3{NQzF5OYXPm5KD%z zAsuIxBd2m2tuf@%7RzOAneltn>G$e)&Og6@&hL4>-p}{@Jg@H`&-e3rU%pT3F-JRj zS#?=h)6yy;e^(n{3O};g3_FIQqdE^+0DyzK3zQKd!!?#BXum#OmS$?#2 zoYB#;pvF8E6&0V1ZJ4}Uw<64-1SQ^6)DTT>;+Irq<0$S==&+%?L5Duu?{^w_V3|Ub zHT4! zW07PWcc9MhO(IszD03CA*fIhqpJfeSFkTZ{u=VG-gahUC+T=%%9;qV`(PnEoZ-QVK zMlqC)lf6CuKnY7m6KIg(s3#{U`(%(2zqHP+hxi;SJr@3FQD91B?{6_gQ&NA2-n;il z5&DuQ<>Yg=`^fKN8O@E(wH_wa(}tc&m*fkeQhI3s^q<4}axLfb^73jXFEs=uGxv1d zQKyN1W#qWVeS@Q= zpV>jZDDJg@=_Q>xa|SE~I=tf(qXyW6LhY10BoQ$E>g*u?WJ8M7wK98YMTDDqv1hHv z%VmkUF@%L?o`PtoChzPx+rT3Y_4hyJo5ZvZl2CKnWaNc~9~F@1cQs6+eInyG*~mUR z=A9-~AYj$7@;9i_mn$nPkR;mTWW~0D_sf|o=$ZblVNekRNt#Mh#fF>N>cT8J(`}m* zHN(xi zCG090%L}o5F)R*_TA@GA2NdMwrZSz(pLca7qC;L+8Uor$G$;Ym}gwM~-wa3R>-tep+kkoWNTnoKo?OyYT z!uiHFkUbjz2lkYg+uGT=Q<(Y7OPY^&=9sV_rq*%!e9xcVxKG;Sbf#y2{(YS@+7SHCDN^yW8fEAcNlC-WJ5| z)BE@Dud^fTc&&do3Tn-NB0cmX0v1 zUaz|60YcjT7m?^GO7tZEEcvEB8eN&G;}pbd3h@k0E3*$!)6MTSE|5FN7Cq26h!XT^ zY0aFObx=#Lv%B%u8&E`;-l-}g92N`tOwj4TuDvBxRKhGf+hkuU!7lPPyy{OC_fB@8 zmV0Y}^y}9HaX6P4b`[Tu(U40=gR_R!4f)V>ux{3tPaNsKlh8yg&MOo%%guiQB@ z5+JXr*oKg@-!w9xZ)gF7kVCb|z;D~g>(tcMab{4{hsW(zp}AzMQji?6iH$e*eCr;o z^Hm3-2cA4xcDqk8k#@Cv#&@$q(}sh1x5nckV>PXj?nY!+|9>V0zb0!$0G=9x$GZs# zlH_J(Sll?SN;&sV)Yo*87s_b9_MkA`_vD1dnsk@B?crnm%c4K};_v&3F8=M~I_2GT zH#4;~le`SyaU6i0oE!rS3qIi*>DK3Lak&zesW1flAetR}-58w1+Y6|TIsiI4I)bJ7 zK3-KZ5oadLu~x&1*B4IO0Fx6Fc!oyIx#9K;#?kKw43J>Qx7I++LJbJRq>i~7rj}ZA zscJ?rYG@tq{lMPdXV18xJ23|v&Nfo16(>SxPwKA$d%fmMuS316W`?mwXq8y!Tl{vh zV=-yD<@>;d1Rp=*r`tuu#XyXUcBV5J-sls1a*n*6#?tSoE(c8YOIX}`Z{N}DN7y6x z`9?Vz?`*NKa0B8^{eAmJ!#8$nIOX|YRUMXs0jgi(W9=6M*mROzAq9DJ@UsDY-uGXd zk6n!qeK0;AM<@OnAO5NkYgY9iL5U|K!QK!2`ZtMvmD)EWO~GF+`D)o?ftuOFoF1i0 z4aVLB=BDj*1%E?oTgiz}t=G+CYBQQ~VnK6DY)SN@S5rKkDfZgWsAVzcY(GwNy%t-w z4Mzp>7aIoemhIrC_Mo65LuMZ!9yLlYbOeC(TnjKLRKcL|;6h_3e*2ETvMjD+*f;u- zB<@*uY+wZ2*9)~w5EdQ=OI zb~nRf`9TfS4Rrd+XN9gmySArm%84>UmiEwxix2$$)ur1h Date: Mon, 2 Dec 2013 19:17:32 -0800 Subject: [PATCH 21/43] DOC/REF: added docstring for axes.bxp and refactored all references to outliers as fliers --- lib/matplotlib/axes/_axes.py | 132 ++++++++++++++++++++++++++++++++--- lib/matplotlib/cbook.py | 2 +- 2 files changed, 123 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 52da6982516f..b4902799eaf0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2878,6 +2878,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, - caps: the horizontal lines at the ends of the whiskers. - fliers: points representing data that extend beyone the whiskers (outliers). + - means: points or lines representing the means. **Example:** @@ -2927,11 +2928,121 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, def bxp(self, bxpstats, positions=None, widths=None, vert=True, patch_artist=False, shownotches=False, showmeans=False, - showcaps=True, showbox=True, boxprops=None, flierprops=None, - medianprops=None, meanprops=None, meanline=False): + showcaps=True, showbox=True, showfliers=True, + boxprops=None, flierprops=None, medianprops=None, + meanprops=None, meanline=False): + """ + Drawing function for box and whisker plots. + + Call signature:: + + bxp(self, bxpstats, positions=None, widths=None, vert=True, + patch_artist=False, shownotches=False, showmeans=False, + showcaps=True, showbox=True, boxprops=None, flierprops=None, + medianprops=None, meanprops=None, meanline=False): + + Make a box and whisker plot for each column of *x* or each + vector in sequence *x*. The box extends from the lower to + upper quartile values of the data, with a line at the median. + The whiskers extend from the box to show the range of the + data. Flier points are those past the end of the whiskers. + + Function Arguments: + + *bxpstats* : + A list of dictionaries containing stats for each boxplot. + Required keys are: + 'med' - The median (scalar float). + 'q1' - The first quartile (25th percentile) (scalar float). + 'q3' - The first quartile (50th percentile) (scalar float). + 'whislo' - Lower bound of the lower whisker (scalar float). + 'whishi' - Upper bound of the upper whisker (scalar float). + Optional keys are + 'mean' - The mean (scalar float). Needed if showmeans=True. + 'fliers' - Data beyond the whiskers (sequence of floats). + Needed if showfliers=True. + 'cilo' & 'ciho' - Lower and upper confidence intervals about + the median. Needed if shownotches=True. + 'label' - Name of the dataset (string). If available, this + will be used a tick label for the boxplot + + *positions* : [ default 1,2,...,n ] + Sets the horizontal positions of the boxes. The ticks and limits + are automatically set to match the positions. + + *widths* : [ default 0.5 ] + Either a scalar or a vector and sets the width of each box. The + default is 0.5, or ``0.15*(distance between extreme positions)`` + if that is smaller. + + *vert* : [ False | True (default) ] + If True (default), makes the boxes vertical. + If False, makes horizontal boxes. + + *patch_artist* : [ False (default) | True ] + If False produces boxes with the Line2D artist + If True produces boxes with the Patch artist1 + + *shownotches* : [ False (default) | True ] + If False (default), produces a rectangular box plot. + If True, will produce a notched box plot + + *showmeans* : [ False (default) | True ] + If True, will toggle one the rendering of the means + + *showcaps* : [ False | True (default) ] + If True, will toggle one the rendering of the caps + + *showbox* : [ False | True (default) ] + If True, will toggle one the rendering of box + + *showfliers* : [ False | True (default) ] + If True, will toggle one the rendering of the fliers + + *boxprops* : [ dict | None (default) ] + If provided, will set the plotting style of the boxes + + *flierprops* : [ dict | None (default) ] + If provided, will set the plotting style of the fliers + + *medianprops* : [ dict | None (default) ] + If provided, will set the plotting style of the medians + + *meanprops* : [ dict | None (default) ] + If provided, will set the plotting style of the means + + *meanline* : [ False (default) | True ] + If True (and *showmeans* is True), will try to render the mean + as a line spanning the full width of the box according to + *meanprops*. Not recommended if *shownotches* is also True. + Otherwise, means will be shown as points. + + Returns a dictionary mapping each component of the boxplot + to a list of the :class:`matplotlib.lines.Line2D` + instances created. That dictionary has the following keys + (assuming vertical boxplots): + - boxes: the main body of the boxplot showing the quartiles + and the median's confidence intervals if enabled. + - medians: horizonal lines at the median of each box. + - whiskers: the vertical lines extending to the most extreme, + n-outlier data points. + - caps: the horizontal lines at the ends of the whiskers. + - fliers: points representing data that extend beyone the + whiskers (fliers). + - means: points or lines representing the means. + + **Example:** + + .. plot:: pyplots/boxplot_demo.py + """ # lists of artists to be output - whiskers, caps, boxes, medians, means, fliers = [], [], [], [], [], [] + whiskers = [] + caps = [] + boxes = [] + medians = [] + means = [] + fliers = [] # empty list of xticklabels datalabels = [] @@ -3039,9 +3150,9 @@ def dopatch(xs, ys, **kwargs): # try to find a new label datalabels.append(stats.get('label', pos)) - # outliers coords - flier_x = np.ones(len(stats['outliers'])) * pos - flier_y = stats['outliers'] + # fliers coords + flier_x = np.ones(len(stats['fliers'])) * pos + flier_y = stats['fliers'] # whisker coords whisker_x = np.ones(2) * pos @@ -3124,10 +3235,11 @@ def dopatch(xs, ys, **kwargs): [pos], [stats['mean']], **meanprops )) - # draw the outliers - fliers.extend(doplot( - flier_x, flier_y, **flierprops - )) + # maybe draw the fliers + if showfliers: + fliers.extend(doplot( + flier_x, flier_y, **flierprops + )) # fix our axes/ticks up a little if vert: diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index ae5e91049542..2dc856e9e8f4 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1975,7 +1975,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap): stats['whislo'] = min(wisklo) # compute a single array of outliers - stats['outliers'] = np.hstack([ + stats['fliers'] = np.hstack([ np.compress(x < stats['whislo'], x), np.compress(x > stats['whishi'], x) ]) From 5cc858f6c6ae1ef68e90af1d2599c3b8102c571b Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 2 Dec 2013 19:19:44 -0800 Subject: [PATCH 22/43] REF/TST: update boxplot_stats test with outliers changed to fliers ENH/TST: boxplots stats now takes a labels kwarg MNT: minor PEP8 whitespace fix --- lib/matplotlib/cbook.py | 22 ++++++++++++++++--- lib/matplotlib/tests/test_cbook.py | 34 ++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 2dc856e9e8f4..520c3ffbf783 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1846,7 +1846,7 @@ def delete_masked_points(*args): return margs -def boxplot_stats(X, whis=1.5, bootstrap=None): +def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): ''' Returns list of dictionaries of staticists to be use to draw a series of box and whisker plots. See the `Returns` section below to the required @@ -1862,7 +1862,7 @@ def boxplot_stats(X, whis=1.5, bootstrap=None): whis : float (default = 1.5) Determines the reach of the whiskers past the first and third - quartiles (e.g., Q3 + whis*IQR). Beyone the whiskers, data are + quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are considers outliers and are plotted as individual points. Set this to an unreasonably high value to force the whiskers to show the min and max data. (IQR = interquartile range, Q3-Q1) @@ -1871,6 +1871,10 @@ def boxplot_stats(X, whis=1.5, bootstrap=None): Number of times the confidence intervals around the median should be bootstrapped (percentile method). + labels : sequence + Labels for each dataset. Length must be compatible with dimensions + of `X` + Returns ------- bxpstats : A list of dictionaries containing the results for each column @@ -1940,9 +1944,21 @@ def _compute_conf_interval(data, med, iqr, bootstrap): X = [X] ncols = len(X) - for ii, x in enumerate(X, start=0): + if labels is None: + labels = [None] * ncols + elif len(labels) != ncols: + raise ValueError("Dimensions of labels and X must be compatible") + + for ii, (x, label) in enumerate(zip(X, labels), start=0): + # empty dict stats = {} + # set the label + if label is not None: + stats['label'] = label + else: + stats['label'] = ii + # arithmetic mean stats['mean'] = np.mean(x) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index efd53c03075e..a00e8b876c8d 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -105,7 +105,7 @@ def setup(self): self.known_keys = sorted([ 'mean', 'med', 'q1', 'q3', 'iqr', 'cilo', 'cihi', 'whislo', 'whishi', - 'outliers' + 'fliers', 'label' ]) self.std_results = cbook.boxplot_stats(self.data) @@ -115,13 +115,14 @@ def setup(self): 'iqr': 13.492709959447094, 'mean': 13.00447442387868, 'med': 3.3335733967038079, - 'outliers': np.array([ + 'fliers': np.array([ 92.55467075, 87.03819018, 42.23204914, 39.29390996 ]), 'q1': 1.3597529879465153, 'q3': 14.85246294739361, 'whishi': 27.899688243699629, - 'whislo': 0.042143774965502923 + 'whislo': 0.042143774965502923, + 'label': 0 } self.known_bootstrapped_ci = { @@ -132,7 +133,11 @@ def setup(self): self.known_whis3_res = { 'whishi': 42.232049135969874, 'whislo': 0.042143774965502923, - 'outliers': np.array([92.55467075, 87.03819018]), + 'fliers': np.array([92.55467075, 87.03819018]), + } + + self.known_res_with_labels = { + 'label': 'Test1' } def test_form_main_list(self): @@ -151,7 +156,7 @@ def test_form_dict_keys(self): def test_results_baseline(self): res = self.std_results[0] for key in list(self.known_nonbootstrapped_res.keys()): - if key != 'outliers': + if key != 'fliers': assert_statement = assert_approx_equal else: assert_statement = assert_array_almost_equal @@ -174,7 +179,7 @@ def test_results_whiskers(self): results = cbook.boxplot_stats(self.data, whis=3) res = results[0] for key in list(self.known_whis3_res.keys()): - if key != 'outliers': + if key != 'fliers': assert_statement = assert_approx_equal else: assert_statement = assert_array_almost_equal @@ -183,3 +188,20 @@ def test_results_whiskers(self): res[key], self.known_whis3_res[key] ) + + def test_results_withlabels(self): + labels = ['Test1', 2, 3, 4] + results = cbook.boxplot_stats(self.data, labels=labels) + res = results[0] + for key in list(self.known_res_with_labels.keys()): + assert_equal(res[key], self.known_res_with_labels[key]) + + @raises(ValueError) + def test_label_error(self): + labels = [1, 2] + results = cbook.boxplot_stats(self.data, labels=labels) + + @raises(ValueError) + def test_bad_dims(self): + data = np.random.normal(size=(34, 34, 34)) + results = cbook.boxplot_stats(data) From e26b943126d4b040648ef58ec40aa594c56ef9d5 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 06:58:31 -0800 Subject: [PATCH 23/43] MNT: reran boilerplate.py --- lib/matplotlib/pyplot.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 5b64afa364f7..3e3024f6b49a 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2600,7 +2600,7 @@ def broken_barh(xranges, yrange, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.boxplot) -def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, +def boxplot(x, notch=False, sym=u'b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, conf_intervals=None, hold=None): ax = gca() @@ -2625,7 +2625,7 @@ def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, @_autogen_docstring(Axes.cohere) def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.window_hanning, noverlap=0, pad_to=None, - sides='default', scale_by_freq=None, hold=None, **kwargs): + sides=u'default', scale_by_freq=None, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2722,7 +2722,7 @@ def csd(x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.errorbar) -def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None, +def errorbar(x, y, yerr=None, xerr=None, fmt=u'-', ecolor=None, elinewidth=None, capsize=3, barsabove=False, lolims=False, uplims=False, xlolims=False, xuplims=False, errorevery=1, capthick=None, hold=None, **kwargs): @@ -2747,8 +2747,8 @@ def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.eventplot) -def eventplot(positions, orientation='horizontal', lineoffsets=1, - linelengths=1, linewidths=None, colors=None, linestyles='solid', +def eventplot(positions, orientation=u'horizontal', lineoffsets=1, + linelengths=1, linewidths=None, colors=None, linestyles=u'solid', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -2825,9 +2825,9 @@ def fill_betweenx(y, x1, x2=0, where=None, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hexbin) -def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', - yscale='linear', extent=None, cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, edgecolors='none', +def hexbin(x, y, C=None, gridsize=100, bins=None, xscale=u'linear', + yscale=u'linear', extent=None, cmap=None, norm=None, vmin=None, + vmax=None, alpha=None, linewidths=None, edgecolors=u'none', reduce_C_function=np.mean, mincnt=None, marginals=False, hold=None, **kwargs): ax = gca() @@ -2853,7 +2853,7 @@ def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', # changes will be lost @_autogen_docstring(Axes.hist) def hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, - bottom=None, histtype='bar', align='mid', orientation='vertical', + bottom=None, histtype=u'bar', align=u'mid', orientation=u'vertical', rwidth=None, log=False, color=None, label=None, stacked=False, hold=None, **kwargs): ax = gca() @@ -2897,7 +2897,7 @@ def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hlines) -def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', +def hlines(y, xmin, xmax, colors=u'k', linestyles=u'solid', label=u'', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3078,7 +3078,8 @@ def plot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.plot_date) -def plot_date(x, y, fmt='o', tz=None, xdate=True, ydate=False, hold=None, + +def plot_date(x, y, fmt=u'bo', tz=None, xdate=True, ydate=False, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3157,7 +3158,7 @@ def quiverkey(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.scatter) -def scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, +def scatter(x, y, s=20, c=u'b', marker=u'o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None, **kwargs): ax = gca() @@ -3295,7 +3296,7 @@ def step(x, y, *args, **kwargs): # changes will be lost @_autogen_docstring(Axes.streamplot) def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, - norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, + norm=None, arrowsize=1, arrowstyle=u'-|>', minlength=0.1, transform=None, zorder=1, hold=None): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3390,7 +3391,7 @@ def triplot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.vlines) -def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', +def vlines(x, ymin, ymax, colors=u'k', linestyles=u'solid', label=u'', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3456,7 +3457,7 @@ def cla(): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.grid) -def grid(b=None, which='major', axis='both', **kwargs): +def grid(b=None, which=u'major', axis=u'both', **kwargs): ret = gca().grid(b=b, which=which, axis=axis, **kwargs) draw_if_interactive() return ret @@ -3504,7 +3505,7 @@ def ticklabel_format(**kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.locator_params) -def locator_params(axis='both', tight=None, **kwargs): +def locator_params(axis=u'both', tight=None, **kwargs): ret = gca().locator_params(axis=axis, tight=tight, **kwargs) draw_if_interactive() return ret @@ -3512,7 +3513,7 @@ def locator_params(axis='both', tight=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.tick_params) -def tick_params(axis='both', **kwargs): +def tick_params(axis=u'both', **kwargs): ret = gca().tick_params(axis=axis, **kwargs) draw_if_interactive() return ret @@ -3528,7 +3529,7 @@ def margins(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.autoscale) -def autoscale(enable=True, axis='both', tight=None): +def autoscale(enable=True, axis=u'both', tight=None): ret = gca().autoscale(enable=enable, axis=axis, tight=tight) draw_if_interactive() return ret From 38368ae70d08ac4cee3ae5b5265f584e466f365e Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 07:02:54 -0800 Subject: [PATCH 24/43] DOC: added blurb in changelog --- CHANGELOG | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 611c3aecbe30..c22426680984 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,13 @@ +<<<<<<< HEAD 2013-12-30 Made streamplot grid size consistent for different types of density argument. A 30x30 grid is now used for both density=1 and density=(1, 1). +2013-12-03 Added a pure boxplot-drawing method that allow a more complete + customization of boxplots. It takes a list of dicts contains stats. + Also created a function (`cbook.boxplot_stats`) that generates the + stats needed. + 2013-11-28 Added qhull extension module to perform Delaunay triangulation more robustly than before. It is used by tri.Triangulation (and hence all pyplot.tri* methods) and mlab.griddata. Deprecated From 0ad290f67b0dc59ec77370088832e42b39f86621 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 07:27:06 -0800 Subject: [PATCH 25/43] MNT: got rid of build-breaking `u''` strings in pyplot --- lib/matplotlib/pyplot.py | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 3e3024f6b49a..8529b4496650 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -132,7 +132,6 @@ def switch_backend(newbackend): def show(*args, **kw): """ Display a figure. - When running in ipython with its pylab mode, display all figures and return to the ipython prompt. @@ -2600,7 +2599,7 @@ def broken_barh(xranges, yrange, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.boxplot) -def boxplot(x, notch=False, sym=u'b+', vert=True, whis=1.5, positions=None, +def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, conf_intervals=None, hold=None): ax = gca() @@ -2625,7 +2624,7 @@ def boxplot(x, notch=False, sym=u'b+', vert=True, whis=1.5, positions=None, @_autogen_docstring(Axes.cohere) def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, window=mlab.window_hanning, noverlap=0, pad_to=None, - sides=u'default', scale_by_freq=None, hold=None, **kwargs): + sides='default', scale_by_freq=None, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2722,7 +2721,7 @@ def csd(x, y, NFFT=None, Fs=None, Fc=None, detrend=None, window=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.errorbar) -def errorbar(x, y, yerr=None, xerr=None, fmt=u'-', ecolor=None, elinewidth=None, +def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None, capsize=3, barsabove=False, lolims=False, uplims=False, xlolims=False, xuplims=False, errorevery=1, capthick=None, hold=None, **kwargs): @@ -2747,8 +2746,8 @@ def errorbar(x, y, yerr=None, xerr=None, fmt=u'-', ecolor=None, elinewidth=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.eventplot) -def eventplot(positions, orientation=u'horizontal', lineoffsets=1, - linelengths=1, linewidths=None, colors=None, linestyles=u'solid', +def eventplot(positions, orientation='horizontal', lineoffsets=1, + linelengths=1, linewidths=None, colors=None, linestyles='solid', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -2825,9 +2824,9 @@ def fill_betweenx(y, x1, x2=0, where=None, hold=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hexbin) -def hexbin(x, y, C=None, gridsize=100, bins=None, xscale=u'linear', - yscale=u'linear', extent=None, cmap=None, norm=None, vmin=None, - vmax=None, alpha=None, linewidths=None, edgecolors=u'none', +def hexbin(x, y, C=None, gridsize=100, bins=None, xscale='linear', + yscale='linear', extent=None, cmap=None, norm=None, vmin=None, + vmax=None, alpha=None, linewidths=None, edgecolors='none', reduce_C_function=np.mean, mincnt=None, marginals=False, hold=None, **kwargs): ax = gca() @@ -2853,7 +2852,7 @@ def hexbin(x, y, C=None, gridsize=100, bins=None, xscale=u'linear', # changes will be lost @_autogen_docstring(Axes.hist) def hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, - bottom=None, histtype=u'bar', align=u'mid', orientation=u'vertical', + bottom=None, histtype='bar', align='mid', orientation='vertical', rwidth=None, log=False, color=None, label=None, stacked=False, hold=None, **kwargs): ax = gca() @@ -2897,7 +2896,7 @@ def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hlines) -def hlines(y, xmin, xmax, colors=u'k', linestyles=u'solid', label=u'', +def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3078,8 +3077,7 @@ def plot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.plot_date) - -def plot_date(x, y, fmt=u'bo', tz=None, xdate=True, ydate=False, hold=None, +def plot_date(x, y, fmt='bo', tz=None, xdate=True, ydate=False, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3158,7 +3156,7 @@ def quiverkey(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.scatter) -def scatter(x, y, s=20, c=u'b', marker=u'o', cmap=None, norm=None, vmin=None, +def scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None, **kwargs): ax = gca() @@ -3296,7 +3294,7 @@ def step(x, y, *args, **kwargs): # changes will be lost @_autogen_docstring(Axes.streamplot) def streamplot(x, y, u, v, density=1, linewidth=None, color=None, cmap=None, - norm=None, arrowsize=1, arrowstyle=u'-|>', minlength=0.1, + norm=None, arrowsize=1, arrowstyle='-|>', minlength=0.1, transform=None, zorder=1, hold=None): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3391,7 +3389,7 @@ def triplot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.vlines) -def vlines(x, ymin, ymax, colors=u'k', linestyles=u'solid', label=u'', +def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False @@ -3457,7 +3455,7 @@ def cla(): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.grid) -def grid(b=None, which=u'major', axis=u'both', **kwargs): +def grid(b=None, which='major', axis='both', **kwargs): ret = gca().grid(b=b, which=which, axis=axis, **kwargs) draw_if_interactive() return ret @@ -3505,7 +3503,7 @@ def ticklabel_format(**kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.locator_params) -def locator_params(axis=u'both', tight=None, **kwargs): +def locator_params(axis='both', tight=None, **kwargs): ret = gca().locator_params(axis=axis, tight=tight, **kwargs) draw_if_interactive() return ret @@ -3513,7 +3511,7 @@ def locator_params(axis=u'both', tight=None, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.tick_params) -def tick_params(axis=u'both', **kwargs): +def tick_params(axis='both', **kwargs): ret = gca().tick_params(axis=axis, **kwargs) draw_if_interactive() return ret @@ -3529,7 +3527,7 @@ def margins(*args, **kw): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @docstring.copy_dedent(Axes.autoscale) -def autoscale(enable=True, axis=u'both', tight=None): +def autoscale(enable=True, axis='both', tight=None): ret = gca().autoscale(enable=enable, axis=axis, tight=tight) draw_if_interactive() return ret From 858964a76f1fb575b324d911805c9a2f03361a8b Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 07:31:55 -0800 Subject: [PATCH 26/43] MNT: fixed changelog whitespace --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c22426680984..bad8881d5e41 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,8 +5,8 @@ 2013-12-03 Added a pure boxplot-drawing method that allow a more complete customization of boxplots. It takes a list of dicts contains stats. - Also created a function (`cbook.boxplot_stats`) that generates the - stats needed. + Also created a function (`cbook.boxplot_stats`) that generates the + stats needed. 2013-11-28 Added qhull extension module to perform Delaunay triangulation more robustly than before. It is used by tri.Triangulation (and hence From 7cf8b357c80c9e1acf78487bf49d2d2fc2683c61 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 13:21:59 -0800 Subject: [PATCH 27/43] ENH: cbook.boxplot_stats can now set the whiskers to -range- (min/max) or specified percentiles. If IQR is zero, min/max are used --- lib/matplotlib/cbook.py | 39 ++++++++++++++++++++------- lib/matplotlib/tests/test_cbook.py | 43 ++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 520c3ffbf783..7a97f969b656 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1860,12 +1860,17 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): Data that will be represented in the boxplots. Should have 2 or fewer dimensions. - whis : float (default = 1.5) - Determines the reach of the whiskers past the first and third - quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are - considers outliers and are plotted as individual points. Set - this to an unreasonably high value to force the whiskers to - show the min and max data. (IQR = interquartile range, Q3-Q1) + whis : float, string, or sequence (default = 1.5) + As a float, determines the reach of the whiskers past the first and + third quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are + considers outliers and are plotted as individual points. Set this + to an unreasonably high value to force the whiskers to show the min + and max data. (IQR = interquartile range, Q3-Q1). Alternatively, set + this to an ascending sequence of percentile (e.g., [5, 95]) to set + the whiskers at specific percentiles of the data. Finally, can be the + string 'range' to force the whiskers to the min and max of the data. + In the edge case that the 25th and 75th percentiles are equivalent, + `whis` will be automatically set to 'range' bootstrap : int or None (default) Number of times the confidence intervals around the median should @@ -1968,14 +1973,31 @@ def _compute_conf_interval(data, med, iqr, bootstrap): # interquartile range stats['iqr'] = stats['q3'] - stats['q1'] + if stats['iqr'] == 0: + whis = 'range' # conf. interval around median stats['cilo'], stats['cihi'] = _compute_conf_interval( x, stats['med'], stats['iqr'], bootstrap ) - # highest non-outliers - hival = stats['q3'] + whis * stats['iqr'] + # lowest/highest non-outliers + if np.isscalar(whis): + if np.isreal(whis): + loval = stats['q1'] - whis * stats['iqr'] + hival = stats['q3'] + whis * stats['iqr'] + elif whis in ['range', 'limit', 'limits', 'min/max']: + loval = np.min(x) + hival = np.max(x) + else: + whismsg = 'whis must be a float, valid string, or '\ + 'list of percentiles' + raise ValueError(whismsg) + else: + loval = np.percentile(x, whis[0]) + hival = np.percentile(x, whis[1]) + + # get high extreme wiskhi = np.compress(x <= hival, x) if len(wiskhi) == 0 or np.max(wiskhi) < stats['q3']: stats['whishi'] = stats['q3'] @@ -1983,7 +2005,6 @@ def _compute_conf_interval(data, med, iqr, bootstrap): stats['whishi'] = max(wiskhi) # get low extreme - loval = stats['q1'] - whis * stats['iqr'] wisklo = np.compress(x >= loval, x) if len(wisklo) == 0 or np.min(wisklo) > stats['q1']: stats['whislo'] = stats['q1'] diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index a00e8b876c8d..994e780911b4 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -101,7 +101,7 @@ def setup(self): self.nrows = 37 self.ncols = 4 self.data = np.random.lognormal(size=(self.nrows, self.ncols), - mean=1.5, sigma=1.75) + mean=1.5, sigma=1.75) self.known_keys = sorted([ 'mean', 'med', 'q1', 'q3', 'iqr', 'cilo', 'cihi', 'whislo', 'whishi', @@ -140,6 +140,17 @@ def setup(self): 'label': 'Test1' } + self.known_res_percentiles = { + 'whislo': 0.1933685896907924, + 'whishi': 42.232049135969874 + } + + self.known_res_range = { + 'whislo': 0.042143774965502923, + 'whishi': 92.554670752188699 + + } + def test_form_main_list(self): assert_true(isinstance(self.std_results, list)) @@ -175,7 +186,7 @@ def test_results_bootstrapped(self): self.known_bootstrapped_ci[key] ) - def test_results_whiskers(self): + def test_results_whiskers_float(self): results = cbook.boxplot_stats(self.data, whis=3) res = results[0] for key in list(self.known_whis3_res.keys()): @@ -189,6 +200,34 @@ def test_results_whiskers(self): self.known_whis3_res[key] ) + def test_results_whiskers_range(self): + results = cbook.boxplot_stats(self.data, whis='range') + res = results[0] + for key in list(self.known_res_range.keys()): + if key != 'fliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_res_range[key] + ) + + def test_results_whiskers_percentiles(self): + results = cbook.boxplot_stats(self.data, whis=[5, 95]) + res = results[0] + for key in list(self.known_res_percentiles.keys()): + if key != 'fliers': + assert_statement = assert_approx_equal + else: + assert_statement = assert_array_almost_equal + + assert_statement( + res[key], + self.known_res_percentiles[key] + ) + def test_results_withlabels(self): labels = ['Test1', 2, 3, 4] results = cbook.boxplot_stats(self.data, labels=labels) From d8da483bd7a59a435060bf2b26fa642628c70d30 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 13:48:32 -0800 Subject: [PATCH 28/43] TST: added tests and baseline images for new whisker behavior --- lib/matplotlib/cbook.py | 4 +- .../test_axes/boxplot_autorange_whiskers.pdf | Bin 0 -> 4786 bytes .../test_axes/boxplot_autorange_whiskers.png | Bin 0 -> 6749 bytes .../test_axes/boxplot_autorange_whiskers.svg | 380 ++++++++++++++++++ .../test_axes/bxp_precentilewhis.png | Bin 0 -> 3469 bytes .../test_axes/bxp_rangewhis.png | Bin 0 -> 3150 bytes lib/matplotlib/tests/test_axes.py | 38 +- 7 files changed, 419 insertions(+), 3 deletions(-) create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 7a97f969b656..d94eecc06102 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -2002,14 +2002,14 @@ def _compute_conf_interval(data, med, iqr, bootstrap): if len(wiskhi) == 0 or np.max(wiskhi) < stats['q3']: stats['whishi'] = stats['q3'] else: - stats['whishi'] = max(wiskhi) + stats['whishi'] = np.max(wiskhi) # get low extreme wisklo = np.compress(x >= loval, x) if len(wisklo) == 0 or np.min(wisklo) > stats['q1']: stats['whislo'] = stats['q1'] else: - stats['whislo'] = min(wisklo) + stats['whislo'] = np.min(wisklo) # compute a single array of outliers stats['fliers'] = np.hstack([ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dfdb5b6f3f9c2ca96467fc467e231512d74cc14e GIT binary patch literal 4786 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 YO&^-}({x3l%mZLzV6I=Rd;Zpc0G9?={r~^~ literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg new file mode 100644 index 000000000000..0d070e52b984 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/boxplot_autorange_whiskers.svg @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_precentilewhis.png new file mode 100644 index 0000000000000000000000000000000000000000..dd6b368e71962f00bfbc46e8e72e68b70979a423 GIT binary patch literal 3469 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png b/lib/matplotlib/tests/baseline_images/test_axes/bxp_rangewhis.png new file mode 100644 index 0000000000000000000000000000000000000000..8df2d30167c8454583f24da67040081f1661fedf GIT binary patch literal 3150 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 G6aNJV$alp6 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 4f9bf7280233..a9b17376a80c 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1048,6 +1048,34 @@ def test_bxp_baseline(): ax.set_yscale('log') ax.bxp(logstats) +@image_comparison(baseline_images=['bxp_rangewhis'], + extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_rangewhis(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)), + whis='range' + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats) + +@image_comparison(baseline_images=['bxp_precentilewhis'], + extensions=['png'], + savefig_kwarg={'dpi': 40}) +def test_bxp_precentilewhis(): + np.random.seed(937) + logstats = matplotlib.cbook.boxplot_stats( + np.random.lognormal(mean=1.25, sigma=1., size=(37,4)), + whis=[5, 95] + ) + + fig, ax = plt.subplots() + ax.set_yscale('log') + ax.bxp(logstats) + @image_comparison(baseline_images=['bxp_with_xlabels'], extensions=['png'], savefig_kwarg={'dpi': 40}) @@ -1305,10 +1333,18 @@ def test_boxplot(): x = np.hstack([-25, x, 25]) fig, ax = plt.subplots() - # show 1 boxplot with mpl medians/conf. interfals, 1 with manual values ax.boxplot([x, x], bootstrap=10000, notch=1) ax.set_ylim((-30, 30)) +@image_comparison(baseline_images=['boxplot_autorange_whiskers']) +def test_boxplot_autorange_whiskers(): + x = np.ones(140) + x = np.hstack([0, x, 2]) + fig, ax = plt.subplots() + + ax.boxplot([x, x], bootstrap=10000, notch=1) + ax.set_ylim((-5, 5)) + @image_comparison(baseline_images=['boxplot_with_CIarray'], remove_text=True, extensions=['png'], savefig_kwarg={'dpi': 40}) From d29bcd7043c2080a7ee60d7ec6dd1e214ce4c9f0 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 17:57:34 -0800 Subject: [PATCH 29/43] ENH: added remaining bxp kwargs to boxplot --- lib/matplotlib/axes/_axes.py | 83 ++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b4902799eaf0..63d4e035e505 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2789,7 +2789,10 @@ def xywhere(xs, ys, mask): def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, - bootstrap=None, usermedians=None, conf_intervals=None): + bootstrap=None, usermedians=None, conf_intervals=None, + meanline=False, showmeans=False, showcaps=True, + showbox=True, showfliers=True, boxprops=None, labels=None, + flierprops=None, medianprops=None, meanprops=None): """ Make a box and whisker plot. @@ -2822,10 +2825,18 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, If True (default), makes the boxes vertical. If False, makes horizontal boxes. - *whis* : [ default 1.5 ] - Defines the length of the whiskers as a function of the inner - quartile range. They extend to the most extreme data point - within ( ``whis*(75%-25%)`` ) data range. + *whis* : [ float | string | or sequence (default = 1.5) ] + As a float, determines the reach of the whiskers past the first + and third quartiles (e.g., Q3 + whis*IQR, IQR = interquartile + range, Q3-Q1). Beyond the whiskers, data are considered outliers + and are plotted as individual points. Set this to an unreasonably + high value to force the whiskers to show the min and max values. + Alternatively, set this to an ascending sequence of percentile + (e.g., [5, 95]) to set the whiskers at specific percentiles of + the data. Finally, can *whis* be the string 'range' to force the + whiskers to the min and max of the data. In the edge case that + the 25th and 75th percentiles are equivalent, *whis* will be + automatically set to 'range'. *bootstrap* : [ *None* (default) | integer ] Specifies whether to bootstrap the confidence intervals @@ -2861,10 +2872,44 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, default is 0.5, or ``0.15*(distance between extreme positions)`` if that is smaller. + *labels* : [ sequence | None (default) ] + Labels for each dataset. Length must be compatible with + dimensions of *x* + *patch_artist* : [ False (default) | True ] If False produces boxes with the Line2D artist If True produces boxes with the Patch artist + *showmeans* : [ False (default) | True ] + If True, will toggle one the rendering of the means + + *showcaps* : [ False | True (default) ] + If True, will toggle one the rendering of the caps + + *showbox* : [ False | True (default) ] + If True, will toggle one the rendering of box + + *showfliers* : [ False | True (default) ] + If True, will toggle one the rendering of the fliers + + *boxprops* : [ dict | None (default) ] + If provided, will set the plotting style of the boxes + + *flierprops* : [ dict | None (default) ] + If provided, will set the plotting style of the fliers + + *medianprops* : [ dict | None (default) ] + If provided, will set the plotting style of the medians + + *meanprops* : [ dict | None (default) ] + If provided, will set the plotting style of the means + + *meanline* : [ False (default) | True ] + If True (and *showmeans* is True), will try to render the mean + as a line spanning the full width of the box according to + *meanprops*. Not recommended if *shownotches* is also True. + Otherwise, means will be shown as points. + Returns a dictionary mapping each component of the boxplot to a list of the :class:`matplotlib.lines.Line2D` instances created. That dictionary has the following keys @@ -2884,12 +2929,11 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, .. plot:: pyplots/boxplot_demo.py """ - bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap) - if sym == 'b+': + bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap, + labels=labels) + if sym == 'b+' and flierprops is None: flierprops = dict(linestyle='none', marker='+', markeredgecolor='blue') - else: - flierprops = sym # replace medians if necessary: if usermedians is not None: @@ -2920,10 +2964,11 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, artists = self.bxp(bxpstats, positions=positions, widths=widths, vert=vert, patch_artist=patch_artist, - shownotches=notch, showmeans=False, - showcaps=True, boxprops=None, - flierprops=flierprops, medianprops=None, - meanprops=None, meanline=False) + shownotches=notch, showmeans=showmeans, + showcaps=showcaps, showbox=showbox, + boxprops=boxprops, flierprops=flierprops, + medianprops=medianprops, meanprops=meanprops, + meanline=meanline, showfliers=showfliers) return artists def bxp(self, bxpstats, positions=None, widths=None, vert=True, @@ -3059,18 +3104,20 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, if boxprops is None: if patch_artist: boxprops = dict(linestyle='solid', edgecolor='black', - facecolor='white') + facecolor='white', linewidth=1) else: - boxprops = dict(linestyle='-', color='black') + boxprops = dict(linestyle='-', color='black', linewidth=1) if patch_artist: otherprops = dict( - linestyle=linestyle_map[boxprops['linestyle']], - color=boxprops['edgecolor'] + linestyle=linestyle_map[boxprops['linestyle']], + color=boxprops['edgecolor'], + linewidth=boxprops.get('linewidth', 1) ) else: otherprops = dict(linestyle=boxprops['linestyle'], - color=boxprops['color']) + color=boxprops['color'], + linewidth=boxprops.get('linewidth', 1)) if flierprops is None: flierprops = dict(linestyle='none', marker='+', From 5ded206dbba6caa4a000f8fe56a2d72a99a276ae Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 17:58:11 -0800 Subject: [PATCH 30/43] DOC: expanded boxplot_stats docstring --- lib/matplotlib/cbook.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index d94eecc06102..a6c37f2b012c 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -1862,12 +1862,12 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): whis : float, string, or sequence (default = 1.5) As a float, determines the reach of the whiskers past the first and - third quartiles (e.g., Q3 + whis*IQR). Beyond the whiskers, data are - considers outliers and are plotted as individual points. Set this - to an unreasonably high value to force the whiskers to show the min - and max data. (IQR = interquartile range, Q3-Q1). Alternatively, set - this to an ascending sequence of percentile (e.g., [5, 95]) to set - the whiskers at specific percentiles of the data. Finally, can be the + third quartiles (e.g., Q3 + whis*IQR, QR = interquartile range, Q3-Q1). + Beyond the whiskers, data are considered outliers and are plotted as + individual points. Set this to an unreasonably high value to force the + whiskers to show the min and max data. Alternatively, set this to an + ascending sequence of percentile (e.g., [5, 95]) to set the whiskers + at specific percentiles of the data. Finally, can `whis` be the string 'range' to force the whiskers to the min and max of the data. In the edge case that the 25th and 75th percentiles are equivalent, `whis` will be automatically set to 'range' From 9b97ac46d90fa457cc8bbda28fe4bd6e5345a8df Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Tue, 3 Dec 2013 23:45:40 -0800 Subject: [PATCH 31/43] DOC: added examples --- examples/statistics/boxplot_demo.py | 75 ++++++++++++++++++++++++++ examples/statistics/bxp_demo.py | 81 +++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 examples/statistics/boxplot_demo.py create mode 100644 examples/statistics/bxp_demo.py diff --git a/examples/statistics/boxplot_demo.py b/examples/statistics/boxplot_demo.py new file mode 100644 index 000000000000..7bc72e30cdf0 --- /dev/null +++ b/examples/statistics/boxplot_demo.py @@ -0,0 +1,75 @@ +""" +Demo of the new boxplot functionality +""" + +import numpy as np +import matplotlib.pyplot as plt + +# fake data +np.random.seed(937) +data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75) +labels = list('ABCD') +fs = 10 # fontsize + +# demonstrate how to toggle the display of different elements: +fig, axes = plt.subplots(nrows=2, ncols=3) +axes[0, 0].boxplot(data, labels=labels) +axes[0, 0].set_title('Default', fontsize=fs) + +axes[0, 1].boxplot(data, labels=labels, showmeans=True) +axes[0, 1].set_title('showmeans=True', fontsize=fs) + +axes[0, 2].boxplot(data, labels=labels, showmeans=True, meanline=True) +axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) + +axes[1, 0].boxplot(data, labels=labels, showbox=False, showcaps=False) +axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) + +axes[1, 1].boxplot(data, labels=labels, notch=True, bootstrap=10000) +axes[1, 1].set_title('notch=True, bootstrap=10000', fontsize=fs) + +axes[1, 2].boxplot(data, labels=labels, showfliers=False) +axes[1, 2].set_title('showfliers=False', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +plt.show() + + +# demonstrate how to customize the display different elements: +boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod') +flierprops = dict(marker='o', markerfacecolor='green', markersize=12, + linestyle='none') +medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick') +meanpointprops = dict(marker='D', markeredgecolor='black', + markerfacecolor='firebrick') +meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') + +fig, axes = plt.subplots(nrows=2, ncols=3) +axes[0, 0].boxplot(data, boxprops=boxprops) +axes[0, 0].set_title('Custom boxprops', fontsize=fs) + +axes[0, 1].boxplot(data, flierprops=flierprops, medianprops=medianprops) +axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) + +axes[0, 2].boxplot(data, whis='range') +axes[0, 2].set_title('whis="range"', fontsize=fs) + +axes[1, 0].boxplot(data, meanprops=meanpointprops, meanline=False, + showmeans=True) +axes[1, 0].set_title('Custom mean as point', fontsize=fs) + +axes[1, 1].boxplot(data, meanprops=meanlineprops, meanline=True, showmeans=True) +axes[1, 1].set_title('Custom mean as line', fontsize=fs) + +axes[1, 2].boxplot(data, whis=[15, 85]) +axes[1, 2].set_title('whis=[15, 85] #percentiles', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +fig.suptitle("I never said they'd be pretty") +plt.show() diff --git a/examples/statistics/bxp_demo.py b/examples/statistics/bxp_demo.py new file mode 100644 index 000000000000..0d413545bc1e --- /dev/null +++ b/examples/statistics/bxp_demo.py @@ -0,0 +1,81 @@ +""" +Demo of the new boxplot drawer function +""" + +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.cbook as cbook + +# fake data +np.random.seed(937) +data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75) +labels = list('ABCD') + +# compute the boxplot stats +stats = cbook.boxplot_stats(data, labels=labels, bootstrap=10000) +# After we've computed the stats, we can go through and change anything. +# Just to prove it, I'll set the median of each set to the median of all +# the data, and double the means +for n in range(len(stats)): + stats[n]['med'] = np.median(data) + stats[n]['mean'] *= 2 + +print(stats[0].keys()) +fs = 10 # fontsize + +# demonstrate how to toggle the display of different elements: +fig, axes = plt.subplots(nrows=2, ncols=3) +axes[0, 0].bxp(stats) +axes[0, 0].set_title('Default', fontsize=fs) + +axes[0, 1].bxp(stats, showmeans=True) +axes[0, 1].set_title('showmeans=True', fontsize=fs) + +axes[0, 2].bxp(stats, showmeans=True, meanline=True) +axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) + +axes[1, 0].bxp(stats, showbox=False, showcaps=False) +axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) + +axes[1, 1].bxp(stats, shownotches=True) +axes[1, 1].set_title('notch=True', fontsize=fs) + +axes[1, 2].bxp(stats, showfliers=False) +axes[1, 2].set_title('showfliers=False', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +plt.show() + + +# demonstrate how to customize the display different elements: +boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod') +flierprops = dict(marker='o', markerfacecolor='green', markersize=12, + linestyle='none') +medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick') +meanpointprops = dict(marker='D', markeredgecolor='black', + markerfacecolor='firebrick') +meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') + +fig, axes = plt.subplots(nrows=2, ncols=2) +axes[0, 0].bxp(stats, boxprops=boxprops) +axes[0, 0].set_title('Custom boxprops', fontsize=fs) + +axes[0, 1].bxp(stats, flierprops=flierprops, medianprops=medianprops) +axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) + +axes[1, 0].bxp(stats, meanprops=meanpointprops, meanline=False, + showmeans=True) +axes[1, 0].set_title('Custom mean as point', fontsize=fs) + +axes[1, 1].bxp(stats, meanprops=meanlineprops, meanline=True, showmeans=True) +axes[1, 1].set_title('Custom mean as line', fontsize=fs) + +for ax in axes.flatten(): + ax.set_yscale('log') + ax.set_yticklabels([]) + +fig.suptitle("I never said they'd be pretty") +plt.show() From 676f28ed37ad4deccfe6efa2b967d50cfeac4b11 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Mon, 16 Dec 2013 13:00:37 -0800 Subject: [PATCH 32/43] DOC: impoved example boxplot figure layout and confirmed that they build with docs --- examples/statistics/boxplot_demo.py | 20 +++++++++++--------- examples/statistics/bxp_demo.py | 16 +++++++++------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/statistics/boxplot_demo.py b/examples/statistics/boxplot_demo.py index 7bc72e30cdf0..f810f6700d7f 100644 --- a/examples/statistics/boxplot_demo.py +++ b/examples/statistics/boxplot_demo.py @@ -12,7 +12,7 @@ fs = 10 # fontsize # demonstrate how to toggle the display of different elements: -fig, axes = plt.subplots(nrows=2, ncols=3) +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6)) axes[0, 0].boxplot(data, labels=labels) axes[0, 0].set_title('Default', fontsize=fs) @@ -20,13 +20,13 @@ axes[0, 1].set_title('showmeans=True', fontsize=fs) axes[0, 2].boxplot(data, labels=labels, showmeans=True, meanline=True) -axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) +axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs) axes[1, 0].boxplot(data, labels=labels, showbox=False, showcaps=False) -axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) +axes[1, 0].set_title('Tufte Style \n(showbox=False,\nshowcaps=False)', fontsize=fs) axes[1, 1].boxplot(data, labels=labels, notch=True, bootstrap=10000) -axes[1, 1].set_title('notch=True, bootstrap=10000', fontsize=fs) +axes[1, 1].set_title('notch=True,\nbootstrap=10000', fontsize=fs) axes[1, 2].boxplot(data, labels=labels, showfliers=False) axes[1, 2].set_title('showfliers=False', fontsize=fs) @@ -35,6 +35,7 @@ ax.set_yscale('log') ax.set_yticklabels([]) +fig.subplots_adjust(hspace=0.4) plt.show() @@ -47,29 +48,30 @@ markerfacecolor='firebrick') meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') -fig, axes = plt.subplots(nrows=2, ncols=3) +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6)) axes[0, 0].boxplot(data, boxprops=boxprops) axes[0, 0].set_title('Custom boxprops', fontsize=fs) axes[0, 1].boxplot(data, flierprops=flierprops, medianprops=medianprops) -axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) +axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs) axes[0, 2].boxplot(data, whis='range') axes[0, 2].set_title('whis="range"', fontsize=fs) axes[1, 0].boxplot(data, meanprops=meanpointprops, meanline=False, showmeans=True) -axes[1, 0].set_title('Custom mean as point', fontsize=fs) +axes[1, 0].set_title('Custom mean\nas point', fontsize=fs) axes[1, 1].boxplot(data, meanprops=meanlineprops, meanline=True, showmeans=True) -axes[1, 1].set_title('Custom mean as line', fontsize=fs) +axes[1, 1].set_title('Custom mean\nas line', fontsize=fs) axes[1, 2].boxplot(data, whis=[15, 85]) -axes[1, 2].set_title('whis=[15, 85] #percentiles', fontsize=fs) +axes[1, 2].set_title('whis=[15, 85]\n#percentiles', fontsize=fs) for ax in axes.flatten(): ax.set_yscale('log') ax.set_yticklabels([]) fig.suptitle("I never said they'd be pretty") +fig.subplots_adjust(hspace=0.4) plt.show() diff --git a/examples/statistics/bxp_demo.py b/examples/statistics/bxp_demo.py index 0d413545bc1e..74d60b0b27bb 100644 --- a/examples/statistics/bxp_demo.py +++ b/examples/statistics/bxp_demo.py @@ -24,7 +24,7 @@ fs = 10 # fontsize # demonstrate how to toggle the display of different elements: -fig, axes = plt.subplots(nrows=2, ncols=3) +fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6,6)) axes[0, 0].bxp(stats) axes[0, 0].set_title('Default', fontsize=fs) @@ -32,10 +32,10 @@ axes[0, 1].set_title('showmeans=True', fontsize=fs) axes[0, 2].bxp(stats, showmeans=True, meanline=True) -axes[0, 2].set_title('showmeans=True, meanline=True', fontsize=fs) +axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs) axes[1, 0].bxp(stats, showbox=False, showcaps=False) -axes[1, 0].set_title('Tufte Style (showbox=False, showcaps=False)', fontsize=fs) +axes[1, 0].set_title('Tufte Style\n(showbox=False,\nshowcaps=False)', fontsize=fs) axes[1, 1].bxp(stats, shownotches=True) axes[1, 1].set_title('notch=True', fontsize=fs) @@ -47,6 +47,7 @@ ax.set_yscale('log') ax.set_yticklabels([]) +fig.subplots_adjust(hspace=0.4) plt.show() @@ -59,23 +60,24 @@ markerfacecolor='firebrick') meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple') -fig, axes = plt.subplots(nrows=2, ncols=2) +fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(6,6)) axes[0, 0].bxp(stats, boxprops=boxprops) axes[0, 0].set_title('Custom boxprops', fontsize=fs) axes[0, 1].bxp(stats, flierprops=flierprops, medianprops=medianprops) -axes[0, 1].set_title('Custom medianprops and flierprops', fontsize=fs) +axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs) axes[1, 0].bxp(stats, meanprops=meanpointprops, meanline=False, showmeans=True) -axes[1, 0].set_title('Custom mean as point', fontsize=fs) +axes[1, 0].set_title('Custom mean\nas point', fontsize=fs) axes[1, 1].bxp(stats, meanprops=meanlineprops, meanline=True, showmeans=True) -axes[1, 1].set_title('Custom mean as line', fontsize=fs) +axes[1, 1].set_title('Custom mean\nas line', fontsize=fs) for ax in axes.flatten(): ax.set_yscale('log') ax.set_yticklabels([]) fig.suptitle("I never said they'd be pretty") +fig.subplots_adjust(hspace=0.4) plt.show() From 954b212df86eaa39bec7590cf3df433027eb263e Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Sun, 12 Jan 2014 20:26:54 -0800 Subject: [PATCH 33/43] DOC: add a blurb in whats_new about the changes to boxplot --- doc/users/whats_new.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 7d059632084a..0b1d7f31065f 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -32,6 +32,31 @@ Phil Elson rewrote of the documentation and userguide for both Legend and PathEf New plotting features --------------------- +Fully customizable boxplots +```````````````````````````` +Paul Hobson overahuled the :func:`~matplotlib.pyplot.boxplot` method such +that it is now completely customizable in terms of the styles and positions +of the individual artists. Under the hood, :func:`~matplotlib.pyplot.boxplot` +relies on a new function (:func:`~matplotlib.cbook.boxplot_stats`), which +accepts any data structure currently compatible with +:func:`~matplotlib.pyplot.boxplot`, amd returns a list of dictaries +containing the positions of each of artists for the boxplots. Then +a second method, :func:`~matplotlib.Axes.bxp` is called to actually +return the stats onto the figure. + +Therefore, it is now possible for the user can use +:func:`~matplotlib.pyplot.boxplot` in the same fashion or generate her own +list of dictionaries of statistics and feed those directio to +:func:`~matplotlib.Axes.bxp`. Similarly, the output from +:func:`~matplotlib.cbook.boxplot_stats` can easily be modified by the user. + +Lastly, each artist (e.g., the box, outliers, cap, notches) can now be +toggled on or off and their styles can be passed in through individual +kwargs. See the examples: +:ref:`~examples/statistics/boxplot_demo.py` and +:ref:`~examples/statistics/bxp_demo.py` + + Support for datetime axes in 2d plots ````````````````````````````````````` Andrew Dawson added support for datetime axes to From d5edad93db45582377e751bff85c5c6ed95ab4d8 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Sun, 12 Jan 2014 20:40:19 -0800 Subject: [PATCH 34/43] removed extra newline in changlog --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f0f030c7e927..480466f4ffd6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,7 +7,6 @@ Also created a function (`cbook.boxplot_stats`) that generates the stats needed. - 2013-11-28 Added qhull extension module to perform Delaunay triangulation more robustly than before. It is used by tri.Triangulation (and hence all pyplot.tri* methods) and mlab.griddata. Deprecated From eb56c3bfef8357c55e218b3a86bfca02feab66fd Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Thu, 16 Jan 2014 22:38:03 -0800 Subject: [PATCH 35/43] boxplot default style dicts now updated with user input and better shape checking for medians --- lib/matplotlib/axes/_axes.py | 104 ++++++++++++++---------------- lib/matplotlib/tests/test_axes.py | 11 +++- 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 63d4e035e505..f6323926d03b 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2937,7 +2937,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, # replace medians if necessary: if usermedians is not None: - if len(usermedians) != len(bxpstats): + if len(np.ravel(usermedians)) != len(bxpstats): medmsg = 'usermedians length not compatible with x' raise ValueError(medmsg) else: @@ -3100,38 +3100,47 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, 'dotted': ':' } - # plotting properties - if boxprops is None: - if patch_artist: - boxprops = dict(linestyle='solid', edgecolor='black', - facecolor='white', linewidth=1) - else: - boxprops = dict(linestyle='-', color='black', linewidth=1) + # box properties + if patch_artist: + final_boxprops = dict(linestyle='solid', edgecolor='black', + facecolor='white', linewidth=1) + else: + final_boxprops = dict(linestyle='-', color='black', linewidth=1) + + if boxprops is not None: + final_boxprops.update(boxprops) + # other (cap, whisker) properties if patch_artist: otherprops = dict( - linestyle=linestyle_map[boxprops['linestyle']], - color=boxprops['edgecolor'], - linewidth=boxprops.get('linewidth', 1) + linestyle=linestyle_map[final_boxprops['linestyle']], + color=final_boxprops['edgecolor'], + linewidth=final_boxprops.get('linewidth', 1) ) else: - otherprops = dict(linestyle=boxprops['linestyle'], - color=boxprops['color'], - linewidth=boxprops.get('linewidth', 1)) - - if flierprops is None: - flierprops = dict(linestyle='none', marker='+', - markeredgecolor='blue') - - if medianprops is None: - medianprops = dict(linestyle='-', color='blue') - - if meanprops is None: - if meanline: - meanprops = dict(linestyle='--', color='red') - else: - meanprops = dict(linestyle='none', markerfacecolor='red', - marker='s') + otherprops = dict(linestyle=final_boxprops['linestyle'], + color=final_boxprops['color'], + linewidth=final_boxprops.get('linewidth', 1)) + + # flier (outlier) properties + final_flierprops = dict(linestyle='none', marker='+', + markeredgecolor='blue') + if flierprops is not None: + final_flierprops.update(flierprops) + + # median line properties + final_medianprops = dict(linestyle='-', color='blue') + if medianprops is not None: + final_medianprops.update(medianprops) + + # mean (line or point) properties + if meanline: + final_meanprops = dict(linestyle='--', color='red') + else: + final_meanprops = dict(linestyle='none', markerfacecolor='red', + marker='s') + if final_meanprops is not None: + final_meanprops.update(meanprops) def to_vc(xs, ys): # convert arguments to verts and codes @@ -3239,54 +3248,37 @@ def dopatch(xs, ys, **kwargs): # maybe draw the box: if showbox: if patch_artist: - boxes.extend(dopatch( - box_x, box_y, **boxprops - )) + boxes.extend(dopatch(box_x, box_y, **final_boxprops)) else: - boxes.extend(doplot( - box_x, box_y, **boxprops - )) + boxes.extend(doplot(box_x, box_y, **final_boxprops)) # draw the whiskers - whiskers.extend(doplot( - whisker_x, whiskerlo_y, **otherprops - )) - whiskers.extend(doplot( - whisker_x, whiskerhi_y, **otherprops - )) + whiskers.extend(doplot(whisker_x, whiskerlo_y, **otherprops)) + whiskers.extend(doplot(whisker_x, whiskerhi_y, **otherprops)) # maybe draw the caps: if showcaps: - caps.extend(doplot( - cap_x, cap_lo, **otherprops - )) - caps.extend(doplot( - cap_x, cap_hi, **otherprops - )) + caps.extend(doplot(cap_x, cap_lo, **otherprops)) + caps.extend(doplot(cap_x, cap_hi, **otherprops)) # draw the medians - medians.extend(doplot( - med_x, med_y, **medianprops - )) + medians.extend(doplot(med_x, med_y, **final_medianprops)) # maybe draw the means if showmeans: if meanline: means.extend(doplot( - [box_left, box_right], - [stats['mean'], stats['mean']], - **meanprops + [box_left, box_right], [stats['mean'], stats['mean']], + **final_meanprops )) else: means.extend(doplot( - [pos], [stats['mean']], **meanprops + [pos], [stats['mean']], **final_meanprops )) # maybe draw the fliers if showfliers: - fliers.extend(doplot( - flier_x, flier_y, **flierprops - )) + fliers.extend(doplot(flier_x, flier_y, **final_flierprops)) # fix our axes/ticks up a little if vert: diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a9b17376a80c..d36c1c5bc8d3 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -1372,12 +1372,18 @@ def test_boxplot_no_weird_whisker(): ax1.yaxis.grid(False, which='minor') ax1.xaxis.grid(False) -def test_boxplot_bad_medians(): +def test_boxplot_bad_medians_1(): x = np.linspace(-7, 7, 140) x = np.hstack([-25, x, 25]) fig, ax = plt.subplots() assert_raises(ValueError, ax.boxplot, x, usermedians=[1, 2]) +def test_boxplot_bad_medians_1(): + x = np.linspace(-7, 7, 140) + x = np.hstack([-25, x, 25]) + fig, ax = plt.subplots() + assert_raises(ValueError, ax.boxplot, [x, x], usermedians=[[1, 2],[1, 2]]) + def test_boxplot_bad_ci_1(): x = np.linspace(-7, 7, 140) x = np.hstack([-25, x, 25]) @@ -1392,8 +1398,7 @@ def test_boxplot_bad_ci_2(): assert_raises(ValueError, ax.boxplot, [x, x], conf_intervals=[[1, 2], [1]]) -@image_comparison(baseline_images=['errorbar_basic', - 'errorbar_mixed']) +@image_comparison(baseline_images=['errorbar_basic', 'errorbar_mixed']) def test_errorbar(): x = np.arange(0.1, 4, 0.5) y = np.exp(-x) From d7a4a8cc10a93646a3cf04bbb093186e64b99264 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Thu, 16 Jan 2014 22:50:26 -0800 Subject: [PATCH 36/43] fixed typo in mean style boxplot updating --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f6323926d03b..6e2a972b43d1 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3139,7 +3139,7 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, else: final_meanprops = dict(linestyle='none', markerfacecolor='red', marker='s') - if final_meanprops is not None: + if meanprops is not None: final_meanprops.update(meanprops) def to_vc(xs, ys): From 1a060815b86693bb75e987afa1fa6abf0895fe55 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Thu, 16 Jan 2014 23:42:25 -0800 Subject: [PATCH 37/43] added some comments to boxplot --- lib/matplotlib/axes/_axes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 6e2a972b43d1..96b37c60c6f8 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3184,11 +3184,13 @@ def dopatch(xs, ys, **kwargs): N = len(bxpstats) datashape_message = "List of boxplot statistics and `{0}` " \ "value must have same length" + # check postision if positions is None: positions = list(xrange(1, N + 1)) elif len(positions) != N: raise ValueError(datashape_message.format("positions")) + # width if widths is None: distance = max(positions) - min(positions) widths = [min(0.15 * max(distance, 1.0), 0.5)] * N @@ -3197,9 +3199,9 @@ def dopatch(xs, ys, **kwargs): elif len(widths) != N: raise ValueError(datashape_message.format("widths")) + # check and save the `hold` state of the current axes if not self._hold: self.cla() - holdStatus = self._hold for pos, width, stats in zip(positions, widths, bxpstats): From da97eab4f73cad517d52f8d02200e69f11826f99 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Sat, 18 Jan 2014 16:27:44 -0800 Subject: [PATCH 38/43] cleanup up typos and minor tweaks based on dev team input --- lib/matplotlib/axes/_axes.py | 6 ++-- lib/matplotlib/cbook.py | 63 +++++++++++++++++------------------- lib/matplotlib/pyplot.py | 2 +- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 96b37c60c6f8..7cf451dee865 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3182,9 +3182,9 @@ def dopatch(xs, ys, **kwargs): # input validation N = len(bxpstats) - datashape_message = "List of boxplot statistics and `{0}` " \ - "value must have same length" - # check postision + datashape_message = ("List of boxplot statistics and `{0}` " + "values must have same the length") + # check position if positions is None: positions = list(xrange(1, N + 1)) elif len(positions) != N: diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index a6c37f2b012c..e9333d9406d9 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -171,13 +171,13 @@ def new_function(): pending : bool, optional If True, uses a PendingDeprecationWarning instead of a DeprecationWarning. - + Example ------- @deprecated('1.4.0') def the_function_to_deprecate(): pass - + """ def deprecate(func, message=message, name=name, alternative=alternative, pending=pending): @@ -1884,6 +1884,15 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None): ------- bxpstats : A list of dictionaries containing the results for each column of data. Keys are as + + Notes + ----- + Non-bootstrapping approach to confidence interval uses Gaussian-based + asymptotic approximation. + + General approach from: + McGill, R., Tukey, J.W., and Larsen, W.A. (1978) "Variations of + Boxplots", The American Statistician, 32:12-16. ''' def _bootstrap_median(data, N=5000): @@ -1891,12 +1900,9 @@ def _bootstrap_median(data, N=5000): M = len(data) percentiles = [2.5, 97.5] - # initialize the array of estimates - estimate = np.empty(N) - for n in range(N): - bsIndex = np.random.random_integers(0, M - 1, M) - bsData = data[bsIndex] - estimate[n] = np.percentile(bsData, 50) + ii = np.random.randint(M, size=(N, M)) + bsData = x[ii] + estimate = np.median(bsData, axis=1, overwrite_input=True) CI = np.percentile(estimate, percentiles) return CI @@ -1909,12 +1915,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap): notch_min = CI[0] notch_max = CI[1] else: - # Estimate notch locations using Gaussian-based - # asymptotic approximation. - # - # For discussion: McGill, R., Tukey, J.W., - # and Larsen, W.A. (1978) "Variations of - # Boxplots", The American Statistician, 32:12-16. + N = len(data) notch_min = med - 1.57 * iqr / np.sqrt(N) notch_max = med + 1.57 * iqr / np.sqrt(N) @@ -1950,48 +1951,42 @@ def _compute_conf_interval(data, med, iqr, bootstrap): ncols = len(X) if labels is None: - labels = [None] * ncols + labels = [str(i) for i in range(ncols)] elif len(labels) != ncols: raise ValueError("Dimensions of labels and X must be compatible") for ii, (x, label) in enumerate(zip(X, labels), start=0): # empty dict stats = {} - - # set the label - if label is not None: - stats['label'] = label - else: - stats['label'] = ii + stats['label'] = label # arithmetic mean stats['mean'] = np.mean(x) # medians and quartiles - stats['q1'], stats['med'], stats['q3'] = \ - np.percentile(x, [25, 50, 75]) + q1, med, q3 = np.percentile(x, [25, 50, 75]) # interquartile range - stats['iqr'] = stats['q3'] - stats['q1'] + stats['iqr'] = q3 - q1 if stats['iqr'] == 0: whis = 'range' # conf. interval around median stats['cilo'], stats['cihi'] = _compute_conf_interval( - x, stats['med'], stats['iqr'], bootstrap + x, med, stats['iqr'], bootstrap ) # lowest/highest non-outliers if np.isscalar(whis): if np.isreal(whis): - loval = stats['q1'] - whis * stats['iqr'] - hival = stats['q3'] + whis * stats['iqr'] + loval = q1 - whis * stats['iqr'] + hival = q3 + whis * stats['iqr'] elif whis in ['range', 'limit', 'limits', 'min/max']: loval = np.min(x) hival = np.max(x) else: - whismsg = 'whis must be a float, valid string, or '\ - 'list of percentiles' + whismsg = ('whis must be a float, valid string, or ' + 'list of percentiles') raise ValueError(whismsg) else: loval = np.percentile(x, whis[0]) @@ -1999,15 +1994,15 @@ def _compute_conf_interval(data, med, iqr, bootstrap): # get high extreme wiskhi = np.compress(x <= hival, x) - if len(wiskhi) == 0 or np.max(wiskhi) < stats['q3']: - stats['whishi'] = stats['q3'] + if len(wiskhi) == 0 or np.max(wiskhi) < q3: + stats['whishi'] = q3 else: stats['whishi'] = np.max(wiskhi) # get low extreme wisklo = np.compress(x >= loval, x) - if len(wisklo) == 0 or np.min(wisklo) > stats['q1']: - stats['whislo'] = stats['q1'] + if len(wisklo) == 0 or np.min(wisklo) > q1: + stats['whislo'] = q1 else: stats['whislo'] = np.min(wisklo) @@ -2017,6 +2012,8 @@ def _compute_conf_interval(data, med, iqr, bootstrap): np.compress(x > stats['whishi'], x) ]) + # add in teh remaining stats and append to final output + stats['q1'], stats['med'], stats['q3'] = q1, med, q3 bxpstats.append(stats) return bxpstats diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 8529b4496650..41672fa67cb4 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3077,7 +3077,7 @@ def plot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.plot_date) -def plot_date(x, y, fmt='bo', tz=None, xdate=True, ydate=False, hold=None, +def plot_date(x, y, fmt='o', tz=None, xdate=True, ydate=False, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False From 437f5c2484428644d83e04ddc0d983a0e7b4baab Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Sat, 18 Jan 2014 16:35:26 -0800 Subject: [PATCH 39/43] cleaned up whats_new (thanks, efiring) --- doc/users/whats_new.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 0b1d7f31065f..0f984255132e 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -34,21 +34,21 @@ New plotting features Fully customizable boxplots ```````````````````````````` -Paul Hobson overahuled the :func:`~matplotlib.pyplot.boxplot` method such +Paul Hobson overhauled the :func:`~matplotlib.pyplot.boxplot` method such that it is now completely customizable in terms of the styles and positions of the individual artists. Under the hood, :func:`~matplotlib.pyplot.boxplot` relies on a new function (:func:`~matplotlib.cbook.boxplot_stats`), which accepts any data structure currently compatible with -:func:`~matplotlib.pyplot.boxplot`, amd returns a list of dictaries -containing the positions of each of artists for the boxplots. Then -a second method, :func:`~matplotlib.Axes.bxp` is called to actually -return the stats onto the figure. - -Therefore, it is now possible for the user can use -:func:`~matplotlib.pyplot.boxplot` in the same fashion or generate her own -list of dictionaries of statistics and feed those directio to -:func:`~matplotlib.Axes.bxp`. Similarly, the output from -:func:`~matplotlib.cbook.boxplot_stats` can easily be modified by the user. +:func:`~matplotlib.pyplot.boxplot`, and returns a list of dictionaries +containing the positions for each element of the boxplots. Then +a second method, :func:`~matplotlib.Axes.bxp` is called to draw the boxplots +based on the stats. + +The :func:~matplotlib.pyplot.boxplot function can be used as before to +generate boxplots from data in one step. But now the user has the +flexibility to generate the statistics independently, or to modify the +output of :func:~matplotlib.cbook.boxplot_stats prior to plotting +with :func:~matplotlib.Axes.bxp. Lastly, each artist (e.g., the box, outliers, cap, notches) can now be toggled on or off and their styles can be passed in through individual From f26a402dfb33caeaab9cc3e0547f4434eeb7af5f Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Sat, 18 Jan 2014 16:46:02 -0800 Subject: [PATCH 40/43] better shaping checking logic --- lib/matplotlib/axes/_axes.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 7cf451dee865..a942aae4e0e5 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2937,25 +2937,26 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, # replace medians if necessary: if usermedians is not None: - if len(np.ravel(usermedians)) != len(bxpstats): + if (len(np.ravel(usermedians)) != len(bxpstats) and + np.shape(usermedians)[0] != len(bxpstats)): medmsg = 'usermedians length not compatible with x' raise ValueError(medmsg) else: + # reassign medians as necessary for stats, med in zip(bxpstats, usermedians): if med is not None: stats['med'] = med if conf_intervals is not None: - if len(conf_intervals) != len(bxpstats): - cimsg = 'conf_intervals length not compatible with x' - raise ValueError(cimsg) + if np.shape(conf_intervals)[0] != len(bxpstats): + raise ValueError('conf_intervals length not ' + 'compatible with x') else: for stats, ci in zip(bxpstats, conf_intervals): if ci is not None: if len(ci) != 2: - cimsg2 = 'each confidence interval must '\ - 'have two values' - raise ValueError(cimsg2) + raise ValueError('each confidence interval must ' + 'have two values') else: if ci[0] is not None: stats['cilo'] = ci[0] From f2aea5b114e70a499c51d3d6aa3b2ac1190749c8 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Sat, 18 Jan 2014 17:13:32 -0800 Subject: [PATCH 41/43] boxplot and bxp docstrings to a more numpy-like format --- lib/matplotlib/axes/_axes.py | 126 +++++++++++++++++++---------------- 1 file changed, 70 insertions(+), 56 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a942aae4e0e5..0f1dae554088 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2798,9 +2798,12 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, Call signature:: - boxplot(x, notch=False, sym='+', vert=True, whis=1.5, + boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, - bootstrap=None, usermedians=None, conf_intervals=None) + bootstrap=None, usermedians=None, conf_intervals=None, + meanline=False, showmeans=False, showcaps=True, + showbox=True, showfliers=True, boxprops=None, labels=None, + flierprops=None, medianprops=None, meanprops=None) Make a box and whisker plot for each column of *x* or each vector in sequence *x*. The box extends from the lower to @@ -2808,24 +2811,25 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, The whiskers extend from the box to show the range of the data. Flier points are those past the end of the whiskers. - Function Arguments: + Parameters + ---------- - *x* : - Array or a sequence of vectors. + x : Array or a sequence of vectors. + The input data. - *notch* : [ False (default) | True ] - If False (default), produces a rectangular box plot. + notch : bool, default = False + If False, produces a rectangular box plot. If True, will produce a notched box plot - *sym* : [ default 'b+' ] + sym : str, default = 'b+' The default symbol for flier points. Enter an empty string ('') if you don't want to show fliers. - *vert* : [ False | True (default) ] + vert : bool, default = False If True (default), makes the boxes vertical. If False, makes horizontal boxes. - *whis* : [ float | string | or sequence (default = 1.5) ] + whis : float, sequence (default = 1.5) or string As a float, determines the reach of the whiskers past the first and third quartiles (e.g., Q3 + whis*IQR, IQR = interquartile range, Q3-Q1). Beyond the whiskers, data are considered outliers @@ -2833,12 +2837,12 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, high value to force the whiskers to show the min and max values. Alternatively, set this to an ascending sequence of percentile (e.g., [5, 95]) to set the whiskers at specific percentiles of - the data. Finally, can *whis* be the string 'range' to force the + the data. Finally, *whis* can be the string 'range' to force the whiskers to the min and max of the data. In the edge case that the 25th and 75th percentiles are equivalent, *whis* will be automatically set to 'range'. - *bootstrap* : [ *None* (default) | integer ] + bootstrap : None (default) or integer Specifies whether to bootstrap the confidence intervals around the median for notched boxplots. If bootstrap==None, no bootstrapping is performed, and notches are calculated @@ -2848,14 +2852,14 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, bootstrap the median to determine it's 95% confidence intervals. Values between 1000 and 10000 are recommended. - *usermedians* : [ default None ] + usermedians : array-like or None (default) An array or sequence whose first dimension (or length) is compatible with *x*. This overrides the medians computed by matplotlib for each element of *usermedians* that is not None. When an element of *usermedians* == None, the median will be - computed directly as normal. + computed by matplotlib as normal. - *conf_intervals* : [ default None ] + conf_intervals : array-like or None (default) Array or sequence whose first dimension (or length) is compatible with *x* and whose second dimension is 2. When the current element of *conf_intervals* is not None, the notch locations computed by @@ -2863,54 +2867,57 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, element of *conf_intervals* is None, boxplot compute notches the method specified by the other kwargs (e.g., *bootstrap*). - *positions* : [ default 1,2,...,n ] - Sets the horizontal positions of the boxes. The ticks and limits + positions : array-like, default = [1, 2, ..., n] + Sets the positions of the boxes. The ticks and limits are automatically set to match the positions. - *widths* : [ default 0.5 ] + widths : array-like, default = 0.5 Either a scalar or a vector and sets the width of each box. The default is 0.5, or ``0.15*(distance between extreme positions)`` if that is smaller. - *labels* : [ sequence | None (default) ] + labels : sequence or None (default) Labels for each dataset. Length must be compatible with dimensions of *x* - *patch_artist* : [ False (default) | True ] + patch_artist : bool, default = False If False produces boxes with the Line2D artist If True produces boxes with the Patch artist - *showmeans* : [ False (default) | True ] + showmeans : bool, default = False If True, will toggle one the rendering of the means - *showcaps* : [ False | True (default) ] + showcaps : bool, default = True If True, will toggle one the rendering of the caps - *showbox* : [ False | True (default) ] + showbox : bool, default = True If True, will toggle one the rendering of box - *showfliers* : [ False | True (default) ] + showfliers : bool, default = True If True, will toggle one the rendering of the fliers - *boxprops* : [ dict | None (default) ] + boxprops : dict or None (default) If provided, will set the plotting style of the boxes - *flierprops* : [ dict | None (default) ] + flierprops : dict or None (default) If provided, will set the plotting style of the fliers - *medianprops* : [ dict | None (default) ] + medianprops : dict or None (default) If provided, will set the plotting style of the medians - *meanprops* : [ dict | None (default) ] + meanprops : dict or None (default) If provided, will set the plotting style of the means - *meanline* : [ False (default) | True ] + meanline : bool, default = False If True (and *showmeans* is True), will try to render the mean as a line spanning the full width of the box according to *meanprops*. Not recommended if *shownotches* is also True. Otherwise, means will be shown as points. - Returns a dictionary mapping each component of the boxplot + Returns + ------- + + A dictionary mapping each component of the boxplot to a list of the :class:`matplotlib.lines.Line2D` instances created. That dictionary has the following keys (assuming vertical boxplots): @@ -2925,9 +2932,10 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, whiskers (outliers). - means: points or lines representing the means. - **Example:** + Examples + -------- - .. plot:: pyplots/boxplot_demo.py + .. plot:: examples/statistics/boxplot_demo.py """ bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap, labels=labels) @@ -2982,10 +2990,11 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, Call signature:: - bxp(self, bxpstats, positions=None, widths=None, vert=True, + bxp(bxpstats, positions=None, widths=None, vert=True, patch_artist=False, shownotches=False, showmeans=False, - showcaps=True, showbox=True, boxprops=None, flierprops=None, - medianprops=None, meanprops=None, meanline=False): + showcaps=True, showbox=True, showfliers=True, + boxprops=None, flierprops=None, medianprops=None, + meanprops=None, meanline=False) Make a box and whisker plot for each column of *x* or each vector in sequence *x*. The box extends from the lower to @@ -2993,9 +3002,10 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, The whiskers extend from the box to show the range of the data. Flier points are those past the end of the whiskers. - Function Arguments: + Parameters + ---------- - *bxpstats* : + bxpstats : list of dicts A list of dictionaries containing stats for each boxplot. Required keys are: 'med' - The median (scalar float). @@ -3012,58 +3022,61 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, 'label' - Name of the dataset (string). If available, this will be used a tick label for the boxplot - *positions* : [ default 1,2,...,n ] - Sets the horizontal positions of the boxes. The ticks and limits + positions : array-like, default = [1, 2, ..., n] + Sets the positions of the boxes. The ticks and limits are automatically set to match the positions. - *widths* : [ default 0.5 ] + widths : array-like, default = 0.5 Either a scalar or a vector and sets the width of each box. The default is 0.5, or ``0.15*(distance between extreme positions)`` if that is smaller. - *vert* : [ False | True (default) ] + vert : bool, default = False If True (default), makes the boxes vertical. If False, makes horizontal boxes. - *patch_artist* : [ False (default) | True ] + patch_artist : bool, default = False If False produces boxes with the Line2D artist - If True produces boxes with the Patch artist1 + If True produces boxes with the Patch artist - *shownotches* : [ False (default) | True ] + shownotches : bool, default = False If False (default), produces a rectangular box plot. If True, will produce a notched box plot - *showmeans* : [ False (default) | True ] + showmeans : bool, default = False If True, will toggle one the rendering of the means - *showcaps* : [ False | True (default) ] + showcaps : bool, default = True If True, will toggle one the rendering of the caps - *showbox* : [ False | True (default) ] + showbox : bool, default = True If True, will toggle one the rendering of box - *showfliers* : [ False | True (default) ] + showfliers : bool, default = True If True, will toggle one the rendering of the fliers - *boxprops* : [ dict | None (default) ] + boxprops : dict or None (default) If provided, will set the plotting style of the boxes - *flierprops* : [ dict | None (default) ] + flierprops : dict or None (default) If provided, will set the plotting style of the fliers - *medianprops* : [ dict | None (default) ] + medianprops : dict or None (default) If provided, will set the plotting style of the medians - *meanprops* : [ dict | None (default) ] + meanprops : dict or None (default) If provided, will set the plotting style of the means - *meanline* : [ False (default) | True ] + meanline : bool, default = False If True (and *showmeans* is True), will try to render the mean as a line spanning the full width of the box according to *meanprops*. Not recommended if *shownotches* is also True. Otherwise, means will be shown as points. - Returns a dictionary mapping each component of the boxplot + Returns + ------- + + A dictionary mapping each component of the boxplot to a list of the :class:`matplotlib.lines.Line2D` instances created. That dictionary has the following keys (assuming vertical boxplots): @@ -3078,9 +3091,10 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, whiskers (fliers). - means: points or lines representing the means. - **Example:** + Examples + -------- - .. plot:: pyplots/boxplot_demo.py + .. plot:: examples/statistics/bxp_demo.py """ # lists of artists to be output whiskers = [] From 9b0118f003a101e7ca2928763d4ada06264230d6 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Sat, 18 Jan 2014 17:19:31 -0800 Subject: [PATCH 42/43] fixed usermedian shape logic (again) --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0f1dae554088..5b63a753fe03 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2945,7 +2945,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, # replace medians if necessary: if usermedians is not None: - if (len(np.ravel(usermedians)) != len(bxpstats) and + if (len(np.ravel(usermedians)) != len(bxpstats) or np.shape(usermedians)[0] != len(bxpstats)): medmsg = 'usermedians length not compatible with x' raise ValueError(medmsg) From 64b11966d70d407f1dc4d2466b676175ec74ee28 Mon Sep 17 00:00:00 2001 From: Paul Hobson Date: Thu, 23 Jan 2014 09:56:08 -0800 Subject: [PATCH 43/43] reran boilerplate.py --- lib/matplotlib/pyplot.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 41672fa67cb4..12480b43552a 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2601,7 +2601,9 @@ def broken_barh(xranges, yrange, hold=None, **kwargs): @_autogen_docstring(Axes.boxplot) def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, widths=None, patch_artist=False, bootstrap=None, usermedians=None, - conf_intervals=None, hold=None): + conf_intervals=None, meanline=False, showmeans=False, showcaps=True, + showbox=True, showfliers=True, boxprops=None, labels=None, + flierprops=None, medianprops=None, meanprops=None, hold=None): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2612,7 +2614,13 @@ def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, ret = ax.boxplot(x, notch=notch, sym=sym, vert=vert, whis=whis, positions=positions, widths=widths, patch_artist=patch_artist, bootstrap=bootstrap, - usermedians=usermedians, conf_intervals=conf_intervals) + usermedians=usermedians, + conf_intervals=conf_intervals, meanline=meanline, + showmeans=showmeans, showcaps=showcaps, + showbox=showbox, showfliers=showfliers, + boxprops=boxprops, labels=labels, + flierprops=flierprops, medianprops=medianprops, + meanprops=meanprops) draw_if_interactive() finally: ax.hold(washold) @@ -2623,8 +2631,8 @@ def boxplot(x, notch=False, sym='b+', vert=True, whis=1.5, positions=None, # changes will be lost @_autogen_docstring(Axes.cohere) def cohere(x, y, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none, - window=mlab.window_hanning, noverlap=0, pad_to=None, - sides='default', scale_by_freq=None, hold=None, **kwargs): + window=mlab.window_hanning, noverlap=0, pad_to=None, sides='default', + scale_by_freq=None, hold=None, **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2746,9 +2754,9 @@ def errorbar(x, y, yerr=None, xerr=None, fmt='-', ecolor=None, elinewidth=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.eventplot) -def eventplot(positions, orientation='horizontal', lineoffsets=1, - linelengths=1, linewidths=None, colors=None, linestyles='solid', - hold=None, **kwargs): +def eventplot(positions, orientation='horizontal', lineoffsets=1, linelengths=1, + linewidths=None, colors=None, linestyles='solid', hold=None, + **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2896,8 +2904,8 @@ def hist2d(x, y, bins=10, range=None, normed=False, weights=None, cmin=None, # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.hlines) -def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', - hold=None, **kwargs): +def hlines(y, xmin, xmax, colors='k', linestyles='solid', label='', hold=None, + **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -3389,8 +3397,8 @@ def triplot(*args, **kwargs): # This function was autogenerated by boilerplate.py. Do not edit as # changes will be lost @_autogen_docstring(Axes.vlines) -def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', - hold=None, **kwargs): +def vlines(x, ymin, ymax, colors='k', linestyles='solid', label='', hold=None, + **kwargs): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold()