From 3c27f6c40263af8b811f8be153b31bed7b7fc522 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 1 May 2020 00:11:04 -0400 Subject: [PATCH 1/7] Remove unnecessary cast in Line3DCollection.set_segments. The `segments` parameter is a list of lines (a line being a 2D array or list of 2-tuples), but every line is not required to be the same length. This would cause NumPy to produce an object array, but it will start to warn about ragged arrays in 1.19. An array isn't really needed as `Line3DCollection._segments3d` is only iterated over. --- lib/mpl_toolkits/mplot3d/art3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 9309371b8476..5ea30204db64 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -257,7 +257,7 @@ def set_segments(self, segments): """ Set 3D segments. """ - self._segments3d = np.asanyarray(segments) + self._segments3d = segments LineCollection.set_segments(self, []) def do_3d_projection(self, renderer): From 2e57d5bd7c9d3b7060cedc9a147597ade31319de Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 1 May 2020 00:56:40 -0400 Subject: [PATCH 2/7] Avoid unnecessary conversions to arrays. These inputs may be ragged, but we only just need to know the length of the items. There's no need to make an array to check that. --- lib/matplotlib/axes/_axes.py | 6 +++--- lib/matplotlib/tests/test_backend_svg.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f2d5fbc7f95a..0e19a4ff9302 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3778,7 +3778,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, stats['med'] = med if conf_intervals is not None: - if np.shape(conf_intervals)[0] != len(bxpstats): + if len(conf_intervals) != len(bxpstats): raise ValueError( "'conf_intervals' and 'x' have different lengths") else: @@ -6555,11 +6555,11 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, if histtype == 'barstacked' and not stacked: stacked = True - # basic input validation - input_empty = np.size(x) == 0 # Massage 'x' for processing. x = cbook._reshape_2D(x, 'x') nx = len(x) # number of datasets + # basic input validation + input_empty = np.size(x) == 0 # Process unit information # Unit conversion is done individually on each dataset diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py index 7bdc3ca33b0a..14d1805f3ffd 100644 --- a/lib/matplotlib/tests/test_backend_svg.py +++ b/lib/matplotlib/tests/test_backend_svg.py @@ -191,7 +191,8 @@ def include(gid, obj): elif obj.axes is None: return False if isinstance(obj, plt.Line2D): - if np.array(obj.get_data()).shape == (2, 1): + xdata, ydata = obj.get_data() + if len(xdata) == len(ydata) == 1: return False elif not hasattr(obj, "axes") or obj.axes is None: return False From ac018af76cae81054dd27c2d4039dfd5467a1366 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 5 May 2020 23:53:00 -0400 Subject: [PATCH 3/7] Rewrite _reshape_2D to not use ragged ndarrays. This is a raising a deprecation warning in NumPy 1.19, may go away some time later, and is not strictly necessary for the implementation. --- lib/matplotlib/cbook/__init__.py | 46 ++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 6209d15643f2..8dbf75899a9d 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1316,24 +1316,48 @@ def _reshape_2D(X, name): Use Fortran ordering to convert ndarrays and lists of iterables to lists of 1D arrays. - Lists of iterables are converted by applying `np.asarray` to each of their - elements. 1D ndarrays are returned in a singleton list containing them. - 2D ndarrays are converted to the list of their *columns*. + Lists of iterables are converted by applying `np.asanyarray` to each of + their elements. 1D ndarrays are returned in a singleton list containing + them. 2D ndarrays are converted to the list of their *columns*. *name* is used to generate the error message for invalid inputs. """ - # Iterate over columns for ndarrays, over rows otherwise. - X = np.atleast_1d(X.T if isinstance(X, np.ndarray) else np.asarray(X)) + # Iterate over columns for ndarrays. + if isinstance(X, np.ndarray): + X = X.T + + if len(X) == 0: + return [[]] + elif X.ndim == 1 and np.ndim(X[0]) == 0: + # 1D array of scalars: directly return it. + return [X] + elif X.ndim in [1, 2]: + # 2D array, or 1D array of iterables: flatten them first. + return [np.reshape(x, -1) for x in X] + else: + raise ValueError(f'{name} must have 2 or fewer dimensions') + + # Iterate over list of iterables. if len(X) == 0: return [[]] - elif X.ndim == 1 and np.ndim(X[0]) == 0: + + result = [] + is_1d = True + for xi in X: + xi = np.asanyarray(xi) + nd = np.ndim(xi) + if nd > 1: + raise ValueError(f'{name} must have 2 or fewer dimensions') + elif nd == 1 and len(xi) != 1: + is_1d = False + result.append(xi.reshape(-1)) + + if is_1d: # 1D array of scalars: directly return it. - return [X] - elif X.ndim in [1, 2]: - # 2D array, or 1D array of iterables: flatten them first. - return [np.reshape(x, -1) for x in X] + return [np.reshape(result, -1)] else: - raise ValueError("{} must have 2 or fewer dimensions".format(name)) + # 2D array, or 1D array of iterables: use flattened version. + return result def violin_stats(X, method, points=100, quantiles=None): From b8b1733e654f8c4c70b2debe7b28b65520fe6e79 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 May 2020 00:21:49 -0400 Subject: [PATCH 4/7] Change hist empty check to not use 2D arrays. This causes a NumPy deprecation warning if the list is ragged. --- lib/matplotlib/axes/_axes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0e19a4ff9302..1eb12b4ca919 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6558,8 +6558,6 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, # Massage 'x' for processing. x = cbook._reshape_2D(x, 'x') nx = len(x) # number of datasets - # basic input validation - input_empty = np.size(x) == 0 # Process unit information # Unit conversion is done individually on each dataset @@ -6581,9 +6579,13 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, if len(w) != nx: raise ValueError('weights should have the same shape as x') + input_empty = True for xi, wi in zip(x, w): - if wi is not None and len(wi) != len(xi): + len_xi = len(xi) + if wi is not None and len(wi) != len_xi: raise ValueError('weights should have the same shape as x') + if len_xi: + input_empty = False if color is None: color = [self._get_lines.get_next_color() for i in range(nx)] From a0aac5a0533a5bc887fe68128f04e91a99e4cf41 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 6 May 2020 00:27:28 -0400 Subject: [PATCH 5/7] Allow object arrays in cbook._combine_masks. This function accepts almost anything list-like, so we really do want conversion to object arrays if the input is ragged. For example, this might occur for `scatter()` if passed different types of `colors`, like in the `test_scatter_marker` test. --- lib/matplotlib/cbook/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 8dbf75899a9d..96598d978b3e 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -989,7 +989,12 @@ def _combine_masks(*args): else: if isinstance(x, np.ma.MaskedArray) and x.ndim > 1: raise ValueError("Masked arrays must be 1-D") - x = np.asanyarray(x) + try: + x = np.asanyarray(x) + except (np.VisibleDeprecationWarning, ValueError): + # NumPy 1.19 raises a warning about ragged arrays, but we want + # to accept basically anything here. + x = np.asanyarray(x, dtype=object) if x.ndim == 1: x = safe_masked_invalid(x) seqlist[i] = True From 98f22a9284083af502745eea5067719ec8aa0daf Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 16 May 2020 03:11:33 -0400 Subject: [PATCH 6/7] Error out of index_of if input is not a NumPy array. --- lib/matplotlib/cbook/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 96598d978b3e..a33da3eab68e 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1606,8 +1606,15 @@ def index_of(y): try: return y.index.values, y.values except AttributeError: + pass + try: y = _check_1d(y) + except (np.VisibleDeprecationWarning, ValueError): + # NumPy 1.19 will warn on ragged input, and we can't actually use it. + pass + else: return np.arange(y.shape[0], dtype=float), y + raise ValueError('Input could not be cast to an at-least-1D NumPy array') def safe_first_element(obj): From 00deb045c06b44a3e87c0f2e77e3af3f1b8345eb Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 16 May 2020 17:25:42 -0400 Subject: [PATCH 7/7] MNT: avoid casting to numpy in violin_stats --- lib/matplotlib/cbook/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index a33da3eab68e..a15b023301b4 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -1428,10 +1428,10 @@ def violin_stats(X, method, points=100, quantiles=None): quantiles = _reshape_2D(quantiles, "quantiles") # Else, mock quantiles if is none or empty else: - quantiles = [[]] * np.shape(X)[0] + quantiles = [[]] * len(X) # quantiles should has the same size as dataset - if np.shape(X)[:1] != np.shape(quantiles)[:1]: + if len(X) != len(quantiles): raise ValueError("List of violinplot statistics and quantiles values" " must have the same length")