diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index bf6e70c15eb3..30b5ec0078b0 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) + + inp = cbook.safe_masked_invalid(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 = [cbook.safe_masked_invalid(arr) for arr in inp] return inp @@ -6117,23 +6122,23 @@ 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(flat) == 0 + input_empty = len(np.ravel(x)) == 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') + # Comparing shape of weights vs. x if len(w) != nx: raise ValueError('weights should have the same shape as x') @@ -6142,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: