From 1d9783b0a522267156e6683a7353c06771b4ca3c Mon Sep 17 00:00:00 2001 From: Trish Gillett-Kawamoto Date: Sat, 16 Jul 2016 10:29:46 -0400 Subject: [PATCH 1/3] Make _normalize_input always output a list of arrays. --- lib/matplotlib/axes/_axes.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index bf6e70c15eb3..a1fdc0518d94 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -5871,7 +5871,7 @@ def hist(self, x, bins=None, range=None, normed=False, weights=None, Parameters ---------- x : (n,) array or sequence of (n,) arrays - Input values, this takes either a single array or a sequency of + Input values, this takes either a single array or a sequence of arrays which are not required to be of the same length bins : integer or array_like or 'auto', optional @@ -6042,8 +6042,8 @@ def hist(self, x, bins=None, range=None, normed=False, weights=None, """ def _normalize_input(inp, ename='input'): - """Normalize 1 or 2d input into list of np.ndarray or - a single 2D np.ndarray. + """Normalize 1 or 2d input into a list of one or more np.ndarrays + which have potentially different lengths. Parameters ---------- @@ -6054,25 +6054,30 @@ def _normalize_input(inp, ename='input'): """ if (isinstance(x, np.ndarray) or not iterable(cbook.safe_first_element(inp))): - # TODO: support masked arrays; + inp = np.asarray(inp) if inp.ndim == 2: # 2-D input with columns as datasets; switch to rows inp = inp.T + + if inp.shape[1] < inp.shape[0]: + warnings.warn( + '2D hist input should be nsamples x nvariables;\n ' + 'this looks transposed ' + '(shape is %d x %d)' % inp.shape[::-1]) + + # Change to a list of arrays + inp = [inp[i, :] for i in range(inp.shape[0])] + elif inp.ndim == 1: - # new view, single row - inp = inp.reshape(1, inp.shape[0]) + # Change to a list with a single array + inp = [inp.reshape(1, inp.shape[0])] else: raise ValueError( "{ename} must be 1D or 2D".format(ename=ename)) - if inp.shape[1] < inp.shape[0]: - warnings.warn( - '2D hist input should be nsamples x nvariables;\n ' - 'this looks transposed ' - '(shape is %d x %d)' % inp.shape[::-1]) else: - # multiple hist with data of different length - inp = [np.asarray(xi) for xi in inp] + # Change to a list of arrays + inp = [np.asarray(arr) for arr in inp] return inp From b16144f5719ba7842bbce697cc7dfecee394e7b0 Mon Sep 17 00:00:00 2001 From: Trish Gillett-Kawamoto Date: Sat, 16 Jul 2016 13:09:37 -0400 Subject: [PATCH 2/3] Tidying up the data prep work in hist. --- lib/matplotlib/axes/_axes.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a1fdc0518d94..9053c6ae688c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6122,22 +6122,21 @@ def _normalize_input(inp, ename='input'): binsgiven = (cbook.iterable(bins) or bin_range is not None) # basic input validation - flat = np.ravel(x) + input_empty = len(np.ravel(x)) == 0 - input_empty = len(flat) == 0 - - # Massage 'x' for processing. + # Massage shape of 'x' to be a list of arrays if input_empty: - x = np.array([[]]) + x = [np.array([[]])] else: x = _normalize_input(x, 'x') nx = len(x) # number of datasets - # We need to do to 'weights' what was done to 'x' - if weights is not None: - w = _normalize_input(weights, 'weights') - else: + # Massage shape of 'weights' to be a list, where each element + # weights[i] is either None or an array with the same shape as x[i] + if weights is None: w = [None]*nx + else: + w = _normalize_input(weights, 'weights') if len(w) != nx: raise ValueError('weights should have the same shape as x') From bf8d2cc41bbef667a1922a6e3a837fdb079593e2 Mon Sep 17 00:00:00 2001 From: Trish Gillett-Kawamoto Date: Sat, 16 Jul 2016 11:30:21 -0400 Subject: [PATCH 3/3] Mask invalid entries in x and weights before plotting histograms. --- lib/matplotlib/axes/_axes.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9053c6ae688c..30b5ec0078b0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6055,7 +6055,7 @@ def _normalize_input(inp, ename='input'): if (isinstance(x, np.ndarray) or not iterable(cbook.safe_first_element(inp))): - inp = np.asarray(inp) + inp = cbook.safe_masked_invalid(inp) if inp.ndim == 2: # 2-D input with columns as datasets; switch to rows inp = inp.T @@ -6077,7 +6077,7 @@ def _normalize_input(inp, ename='input'): "{ename} must be 1D or 2D".format(ename=ename)) else: # Change to a list of arrays - inp = [np.asarray(arr) for arr in inp] + inp = [cbook.safe_masked_invalid(arr) for arr in inp] return inp @@ -6138,6 +6138,7 @@ def _normalize_input(inp, ename='input'): else: w = _normalize_input(weights, 'weights') + # Comparing shape of weights vs. x if len(w) != nx: raise ValueError('weights should have the same shape as x') @@ -6146,6 +6147,16 @@ def _normalize_input(inp, ename='input'): raise ValueError( 'weights should have the same shape as x') + # Combine the masks from x[i] and w[i] (if applicable) into a single + # mask and apply it to both. + if not input_empty: + for i in range(len(x)): + mask_i = x[i].mask + if w[i] is not None: + mask_i = mask_i | w[i].mask + w[i] = np.ma.masked_array(w[i], mask=mask_i).compressed() + x[i] = np.ma.masked_array(x[i], mask=mask_i).compressed() + if color is None: color = [self._get_lines.get_next_color() for i in xrange(nx)] else: