From de5e40a9c175ac9e5754274724c4c7bc8a7becc4 Mon Sep 17 00:00:00 2001 From: Miguel Gaiowski Date: Mon, 14 Oct 2013 23:22:09 -0300 Subject: [PATCH 1/6] Adds option to plot average in boxplot, besides the median --- lib/matplotlib/axes/_axes.py | 53 +++++++++++++++++++++++++++++++++--- lib/matplotlib/pyplot.py | 5 ++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index da8d32eb7f8d..0d327908d682 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2788,7 +2788,8 @@ 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, + averages=False, useraverages=None): """ Make a box and whisker plot. @@ -2796,7 +2797,8 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, boxplot(x, notch=False, sym='+', 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, + averages=False, useraverages=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 @@ -2851,6 +2853,17 @@ 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*). + *averages* : [ False (default) | True ] + If False (default), does not include an average line. + If True, will produce a line in the box with the average value. + + *useraverages* : [ default None ] + An array or sequence whose first dimension (or length) is + compatible with *x*. This overrides the averages computed by + matplotlib for each element of *useraverages* that is not None. + When an element of *useraverages* == None, the median will be + computed directly as normal. + *positions* : [ default 1,2,...,n ] Sets the horizontal positions of the boxes. The ticks and limits are automatically set to match the positions. @@ -2872,6 +2885,7 @@ def boxplot(self, x, notch=False, sym='b+', vert=True, whis=1.5, - 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. + - averages: horizonal lines at the average 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. @@ -2916,7 +2930,7 @@ def computeConfInterval(data, med, iq, bootstrap): if not self._hold: self.cla() holdStatus = self._hold - whiskers, caps, boxes, medians, fliers = [], [], [], [], [] + whiskers, caps, boxes, medians, average_values, fliers = [], [], [], [], [], [] # convert x to a list of vectors if hasattr(x, 'shape'): @@ -2951,6 +2965,19 @@ def computeConfInterval(data, med, iq, bootstrap): elif len(usermedians) != col: raise ValueError(msg2) + if averages: + # sanitize user-input averages + msg1 = "useraverages must either be a list/tuple or a 1d array" + msg2 = "useraverages' length must be compatible with x" + if useraverages is not None: + if hasattr(useraverages, 'shape'): + if len(useraverages.shape) != 1: + raise ValueError(msg1) + elif useraverages.shape[0] != col: + raise ValueError(msg2) + elif len(useraverages) != 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" @@ -2996,6 +3023,15 @@ def computeConfInterval(data, med, iq, bootstrap): if usermedians[i] is not None: med = usermedians[i] + if averages: + # get average + avg = np.average(d) + + # replace with input averages if available + if useraverages is not None: + if useraverages[i] is not None: + avg = useraverages[i] + # get high extreme iq = q3 - q1 hi_val = q3 + whis * iq @@ -3037,6 +3073,10 @@ def computeConfInterval(data, med, iq, bootstrap): # get y location for median med_y = [med, med] + if averages: + # get y location for average + avg_y = [avg, avg] + # calculate 'notch' plot if notch: # conf. intervals from user, if available @@ -3064,6 +3104,9 @@ def computeConfInterval(data, med, iq, bootstrap): box_y = [q1, q1, q3, q3, q1] # make our median line vectors med_x = [box_x_min, box_x_max] + if averages: + # make our average line vectors + avg_x = [box_x_min, box_x_max] def to_vc(xs, ys): # convert arguments to verts and codes @@ -3119,6 +3162,8 @@ def dopatch(xs, ys): boxes.extend(doplot(box_x, box_y, 'b-')) medians.extend(doplot(med_x, med_y, median_color + '-')) + if averages: + average_values.extend(doplot(avg_x, avg_y, 'k:')) fliers.extend(doplot(flier_hi_x, flier_hi, sym, flier_lo_x, flier_lo, sym)) @@ -3136,7 +3181,7 @@ def dopatch(xs, ys): self.hold(holdStatus) return dict(whiskers=whiskers, caps=caps, boxes=boxes, - medians=medians, fliers=fliers) + medians=medians, averages=average_values, fliers=fliers) @docstring.dedent_interpd def scatter(self, x, y, s=20, c='b', marker='o', cmap=None, norm=None, diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 59d3fb5a51c4..b01068811c34 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2568,7 +2568,7 @@ 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, averages=False, useraverages=None, hold=None): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() @@ -2579,7 +2579,8 @@ 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, + averages=averages, useraverages=useraverages) draw_if_interactive() finally: ax.hold(washold) From d6bc7277aeffee4bd96da7d5567ac9bbf38ad27e Mon Sep 17 00:00:00 2001 From: Miguel Gaiowski Date: Tue, 15 Oct 2013 11:01:11 -0300 Subject: [PATCH 2/6] Moved new args to the end of the arglist --- lib/matplotlib/pyplot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index b01068811c34..8264c72a5082 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2568,7 +2568,7 @@ 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, averages=False, useraverages=None, hold=None): + conf_intervals=None, hold=None, averages=False, useraverages=None): ax = gca() # allow callers to override the hold state by passing hold=True|False washold = ax.ishold() From 02bd4ada50bd9985c0823b5cb6907b5458d96fea Mon Sep 17 00:00:00 2001 From: Miguel Gaiowski Date: Tue, 15 Oct 2013 11:14:00 -0300 Subject: [PATCH 3/6] Added documentation as coding guide requires --- doc/api/api_changes.rst | 4 ++++ doc/users/whats_new.rst | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index ac2df22c3f8a..cd96bae8d1fe 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -14,6 +14,10 @@ For new features that were added to matplotlib, please see Changes in 1.4.x ================ +* Option to display averages in boxplots introduces two new arguments + to :func:`~matplotlib.pyplot.boxplot`. Arguments `averages` and + `useraverages`. + * A major refactoring of the axes module was made. The axes module has been splitted into smaller modules: diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 5ea30312e7da..2e793883aae2 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -29,8 +29,13 @@ New plotting features Support for datetime axes in 2d plots ````````````````````````````````````` Andrew Dawson added support for datetime axes to -:func:`~matplotlib.pyplot.contour`, :func:`~matplotlib.pyplot.contourf`, -:func:`~matplotlib.pyplot.pcolormesh` and :func:`~matplotlib.pyplot.pcolor`. +:func:`~matplotlib.pyplot.contour`, :func:`~matplotlib.pyplot.contourf`, +:func:`~matplotlib.pyplot.pcolormesh` and :func:`~matplotlib.pyplot.pcolor`. + +Display average in boxplots, besides de median value +````````````````````````````````````` +Miguel Gaiowski added option to display the average in boxplots +:func:`~matplotlib.pyplot.boxplot`. Date handling @@ -46,10 +51,10 @@ conversion interfaces :class:`matplotlib.dates.DateConverter` and Configuration (rcParams) ------------------------ - + ``savefig.transparent`` added ````````````````````````````` -Controls whether figures are saved with a transparent +Controls whether figures are saved with a transparent background by default. Previously `savefig` always defaulted to a non-transparent background. From 42e8614e66ef170f396484c86656b68597e03760 Mon Sep 17 00:00:00 2001 From: Miguel Gaiowski Date: Tue, 15 Oct 2013 12:03:35 -0300 Subject: [PATCH 4/6] Split line to be under 80 chars long --- lib/matplotlib/axes/_axes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0d327908d682..d69e3c706a76 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2930,7 +2930,8 @@ def computeConfInterval(data, med, iq, bootstrap): if not self._hold: self.cla() holdStatus = self._hold - whiskers, caps, boxes, medians, average_values, fliers = [], [], [], [], [], [] + whiskers, caps, boxes = [], [], [] + medians, average_values, fliers = [], [], [] # convert x to a list of vectors if hasattr(x, 'shape'): From c75c67a891f0c9177fc1cacfc09ccf59e3fac596 Mon Sep 17 00:00:00 2001 From: Miguel Gaiowski Date: Tue, 15 Oct 2013 12:35:36 -0300 Subject: [PATCH 5/6] Only calculate averages if necessary --- lib/matplotlib/axes/_axes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index d69e3c706a76..b6527e221e87 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3025,13 +3025,13 @@ def computeConfInterval(data, med, iq, bootstrap): med = usermedians[i] if averages: - # get average - avg = np.average(d) + # use input averages if available + if useraverages is not None and useraverages[i] is not None: + avg = useraverages[i] + else: + avg = np.average(d) + - # replace with input averages if available - if useraverages is not None: - if useraverages[i] is not None: - avg = useraverages[i] # get high extreme iq = q3 - q1 From d79c099d36cf47fcb4b4a42f563ca81dc6eedad3 Mon Sep 17 00:00:00 2001 From: Miguel Gaiowski Date: Tue, 15 Oct 2013 14:11:18 -0300 Subject: [PATCH 6/6] Remove whitespace --- lib/matplotlib/axes/_axes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index b6527e221e87..1b33ad34db39 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3031,8 +3031,6 @@ def computeConfInterval(data, med, iq, bootstrap): else: avg = np.average(d) - - # get high extreme iq = q3 - q1 hi_val = q3 + whis * iq