From 7d43ef3cf4125e5381ff37b4cbe8053de967d341 Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Sat, 2 Feb 2019 13:24:24 +0000 Subject: [PATCH 1/8] refactored line block logic --- animatplot/blocks/lineplots.py | 92 ++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 21 deletions(-) diff --git a/animatplot/blocks/lineplots.py b/animatplot/blocks/lineplots.py index 96bbf53..3079a6d 100644 --- a/animatplot/blocks/lineplots.py +++ b/animatplot/blocks/lineplots.py @@ -1,16 +1,21 @@ +from warnings import warn +import numpy as np + from .base import Block from animatplot.util import parametric_line -import numpy as np -from warnings import warn class Line(Block): - """Animates lines + """ + Animates a single line. + + Accepts additional keyword arguments to be passed to + :meth:`matplotlib.axes.Axes.plot`. Parameters ---------- - x : list of 1D numpy arrays or a 2D numpy array - The x data to be animated. + x : 1D numpy array, list of 1D numpy arrays or a 2D numpy array, optional + The x data to be animated. If 1D then will be constant over animation. y : list of 1D numpy arrays or a 2D numpy array The y data to be animated. ax : matplotlib.axes.Axes, optional @@ -22,42 +27,87 @@ class Line(Block): The default is chosen to be consistent with: X, T = numpy.meshgrid(x, t) + **kwargs + Passed on to `matplotlib.axes.Axes.plot`. Attributes ---------- + line: matplotlib.lines.Line2D + ax : matplotlib.axes.Axes The matplotlib axes that the block is attached to. Notes ----- - This block accepts additional keyword arguments to be passed to - :meth:`matplotlib.axes.Axes.plot` + This block animates a single line - to animate multiple lines you must call + this once for each line, and then animate all of the blocks returned by + passing a list of those blocks to `animatplot.animation.Animation`. """ - def __init__(self, x, y, ax=None, t_axis=0, **kwargs): + + def __init__(self, *args, ax=None, t_axis=0, **kwargs): axis = kwargs.pop('axis', None) if axis is not None: warn('axis has been replaced in favour of "ax", ' 'and will be removed in 0.4.0.') ax = axis - self.x = np.asanyarray(x) + super().__init__(ax, t_axis) + + # TODO handle lists by instead just converting straight to ndarrays? + # TODO option for x being specified as an unvarying 1D array? + + if len(args) == 1: + y = args[0] + x = None + elif len(args) == 2: + [x, y] = args + else: + raise ValueError("Invalid data arguments to Line block") + + if y is None: + raise ValueError("Must supply y data to plot") self.y = np.asanyarray(y) - if self.x.shape != self.y.shape: + if y.ndim != 2: + raise ValueError("y data must be 2-dimensional") + + # x is optional + if x is None: + x = np.arange(y.shape[t_axis]) + else: + x = np.asanyarray(x) + + # x might be constant over time + if x.ndim == 1: + # TODO better way to specify "not time dimension" + if x.shape[0] == y.shape[t_axis-1]: + # Broadcast x to match y + x = np.repeat(x[..., np.newaxis], repeats=y.shape[t_axis], + axis=t_axis) + + print(x.shape) + print(y.shape) + if x.shape != y.shape: + # TODO more informative error message raise ValueError("x, y must have the same shape" "or be lists of the same length") - super().__init__(ax, t_axis) - self._is_list = (self.x.dtype == 'object') - Slice = self._make_slice(0, 2) - self.line, = self.ax.plot(self.x[Slice], self.y[Slice], **kwargs) + self.x = x + self.y = y - def _update(self, i): - Slice = self._make_slice(i, 2) - x_vector = self.x[Slice] - y_vector = self.y[Slice] + self._is_list = isinstance(self.x, list) + frame_slice = self._make_slice(0, 2) + + x_first_frame_data = self.x[frame_slice] + y_first_frame_data = self.y[frame_slice] + + self.line, = self.ax.plot(x_first_frame_data, + y_first_frame_data, **kwargs) + def _update(self, frame): + frame_slice = self._make_slice(frame, dim=2) + x_vector = self.x[frame_slice] + y_vector = self.y[frame_slice] self.line.set_data(x_vector, y_vector) - return self.line def __len__(self): if self._is_list: @@ -86,8 +136,8 @@ class ParametricLine(Line): :meth:`matplotlib.axes.Axes.plot` """ def __init__(self, x, y, *args, **kwargs): - X, Y = parametric_line(x, y) - super().__init__(X, Y, *args, *kwargs) + x_grid, y_grid = parametric_line(x, y) + super().__init__(x_grid, y_grid, *args, *kwargs) class Scatter(Block): From 8c28ce0fcab721b9cd81972567683ebd7ff872fb Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Sat, 2 Feb 2019 13:24:51 +0000 Subject: [PATCH 2/8] Added some unit tests for line block --- animatplot/blocks/lineplots.py | 43 +++++++------- tests/test_blocks.py | 102 +++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 22 deletions(-) diff --git a/animatplot/blocks/lineplots.py b/animatplot/blocks/lineplots.py index 3079a6d..1454cb5 100644 --- a/animatplot/blocks/lineplots.py +++ b/animatplot/blocks/lineplots.py @@ -53,9 +53,6 @@ def __init__(self, *args, ax=None, t_axis=0, **kwargs): super().__init__(ax, t_axis) - # TODO handle lists by instead just converting straight to ndarrays? - # TODO option for x being specified as an unvarying 1D array? - if len(args) == 1: y = args[0] x = None @@ -66,36 +63,40 @@ def __init__(self, *args, ax=None, t_axis=0, **kwargs): if y is None: raise ValueError("Must supply y data to plot") - self.y = np.asanyarray(y) + y = np.asanyarray(y) if y.ndim != 2: raise ValueError("y data must be 2-dimensional") # x is optional + shape = list(y.shape) + shape.remove(y.shape[t_axis]) + data_length, = shape if x is None: - x = np.arange(y.shape[t_axis]) + x = np.arange(data_length) else: x = np.asanyarray(x) - # x might be constant over time + shape_mismatch = "The dimensions of x must be compatible with those " \ + "of y, but the shape of x is {} and the shape of y " \ + "is {}".format(x.shape, y.shape) if x.ndim == 1: - # TODO better way to specify "not time dimension" - if x.shape[0] == y.shape[t_axis-1]: + # x is constant over time + if len(x) == data_length: # Broadcast x to match y - x = np.repeat(x[..., np.newaxis], repeats=y.shape[t_axis], - axis=t_axis) - - print(x.shape) - print(y.shape) - if x.shape != y.shape: - # TODO more informative error message - raise ValueError("x, y must have the same shape" - "or be lists of the same length") + x = np.expand_dims(x, axis=t_axis) + x = np.repeat(x, repeats=y.shape[t_axis], axis=t_axis) + else: + raise ValueError(shape_mismatch) + elif x.ndim == 2: + if x.shape != y.shape: + raise ValueError(shape_mismatch) + else: + raise ValueError("x, must be either 1- or 2-dimensional") self.x = x self.y = y - self._is_list = isinstance(self.x, list) - frame_slice = self._make_slice(0, 2) + frame_slice = self._make_slice(i=0, dim=2) x_first_frame_data = self.x[frame_slice] y_first_frame_data = self.y[frame_slice] @@ -110,9 +111,7 @@ def _update(self, frame): self.line.set_data(x_vector, y_vector) def __len__(self): - if self._is_list: - return self.x.shape[0] - return self.x.shape[self.t_axis] + return self.y.shape[self.t_axis] class ParametricLine(Line): diff --git a/tests/test_blocks.py b/tests/test_blocks.py index 45f776e..2ea3ee0 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -1,6 +1,8 @@ from matplotlib.testing import setup import numpy as np +import numpy.testing as npt import matplotlib.pyplot as plt +import matplotlib as mpl import pytest @@ -69,6 +71,106 @@ def test_mpl_kwargs(self): assert actual._mpl_kwargs == expected +class TestLineBlock: + def test_2d_inputs(self): + x = np.linspace(0, 1, 10) + t = np.linspace(0, 1, 5) + x_grid, t_grid = np.meshgrid(x, t) + y_data = np.sin(2 * np.pi * (x_grid + t_grid)) + + line_block = amp.blocks.Line(x_grid, y_data) + + assert isinstance(line_block, amp.blocks.Line) + npt.assert_equal(line_block.x, x_grid) + npt.assert_equal(line_block.y, y_data) + assert len(line_block) == len(t) + + assert isinstance(line_block.line, mpl.lines.Line2D) + xdata, ydata = line_block.line.get_data() + npt.assert_equal(xdata, x) + npt.assert_equal(ydata, y_data[0, :]) + + def test_update(self): + x = np.linspace(0, 1, 10) + t = np.linspace(0, 1, 5) + x_grid, t_grid = np.meshgrid(x, t) + y_data = np.sin(2 * np.pi * (x_grid + t_grid)) + + line_block = amp.blocks.Line(x_grid, y_data) + line_block._update(frame=1) + + npt.assert_equal(line_block.line.get_xdata(), x) + npt.assert_equal(line_block.line.get_ydata(), y_data[1, :]) + + def test_constant_x(self): + x = np.linspace(0, 1, 10) + t = np.linspace(0, 1, 5) + x_grid, t_grid = np.meshgrid(x, t) + y_data = np.sin(2 * np.pi * (x_grid + t_grid)) + + line_block = amp.blocks.Line(y_data) + + expected_x = np.arange(10) + npt.assert_equal(line_block.line.get_xdata(), expected_x) + + def test_no_x_input(self): + x = np.linspace(0, 1, 10) + t = np.linspace(0, 1, 5) + x_grid, t_grid = np.meshgrid(x, t) + y_data = np.sin(2 * np.pi * (x_grid + t_grid)) + + line_block = amp.blocks.Line(y_data) + + expected_x = np.arange(10) + npt.assert_equal(line_block.line.get_xdata(), expected_x) + + def test_list_input(self): + x_data = [np.array([1, 2, 3]), np.array([1, 2, 3])] + y_data = [np.array([5, 6, 7]), np.array([4, 2, 9])] + line_block = amp.blocks.Line(x_data, y_data) + npt.assert_equal(line_block.y, np.array([[5, 6, 7], [4, 2, 9]])) + npt.assert_equal(line_block.x, np.array([[1, 2, 3], [1, 2, 3]])) + + def test_bad_input(self): + # incorrect number of args + with pytest.raises(ValueError) as err: + amp.blocks.Line(1, 2, 3) + assert 'Invalid data arguments' in str(err.value) + with pytest.raises(ValueError) as err: + amp.blocks.Line() + assert 'Invalid data arguments' in str(err.value) + + # No y data + with pytest.raises(ValueError) as err: + amp.blocks.Line(np.arange(5), None) + assert 'Must supply y data' in str(err.value) + with pytest.raises(ValueError) as err: + amp.blocks.Line(None) + assert 'Must supply y data' in str(err.value) + + # y data not 2d + with pytest.raises(ValueError) as err: + amp.blocks.Line(np.arange(5), np.random.randn(5, 2, 2)) + assert 'y data must be 2-dimensional' in str(err.value) + + # 1d x doesn't match y + with pytest.raises(ValueError) as err: + amp.blocks.Line(np.arange(5), np.random.randn(4, 2)) + assert 'dimensions of x must be compatible' in str(err.value) + + # 2d x doesn't match y + with pytest.raises(ValueError) as err: + x = np.array([np.arange(5), np.arange(5)]) + amp.blocks.Line(x, np.random.randn(4, 2), t_axis=1) + assert 'dimensions of x must be compatible' in str(err.value) + + def test_kwarg_throughput(self): + x = np.array([np.arange(5), np.arange(5)]) + line_block = amp.blocks.Line(x, np.random.randn(2, 5), t_axis=1, + alpha=0.5) + assert line_block.line.get_alpha() == 0.5 + + class TestComparisons: @animation_compare(baseline_images='Blocks/Line', nframes=5) def test_Line(self): From da795528d22da9e0a74afd8b7181005dd4cb8298 Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Mon, 4 Feb 2019 13:29:14 +0000 Subject: [PATCH 3/8] Fixed no_x_input test --- animatplot/blocks/lineplots.py | 7 +++++-- tests/test_blocks.py | 14 +++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/animatplot/blocks/lineplots.py b/animatplot/blocks/lineplots.py index 91b569d..d007cd7 100644 --- a/animatplot/blocks/lineplots.py +++ b/animatplot/blocks/lineplots.py @@ -57,8 +57,11 @@ def __init__(self, *args, ax=None, t_axis=0, **kwargs): if y is None: raise ValueError("Must supply y data to plot") y = np.asanyarray(y) - if y.ndim != 2: - raise ValueError("y data must be 2-dimensional") + if not all(len(l) == len(y[0]) for l in y): + raise NotImplementedError("Ragged array!") + else: + if y.ndim != 2: + raise ValueError("y data must be 2-dimensional") # x is optional shape = list(y.shape) diff --git a/tests/test_blocks.py b/tests/test_blocks.py index 2946f0c..125f140 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -108,10 +108,10 @@ def test_constant_x(self): x_grid, t_grid = np.meshgrid(x, t) y_data = np.sin(2 * np.pi * (x_grid + t_grid)) - line_block = amp.blocks.Line(y_data) + line_block = amp.blocks.Line(x, y_data) - expected_x = np.arange(10) - npt.assert_equal(line_block.line.get_xdata(), expected_x) + npt.assert_equal(line_block.line.get_xdata(), x) + npt.assert_equal(line_block.x[-1], x) def test_no_x_input(self): x = np.linspace(0, 1, 10) @@ -131,6 +131,14 @@ def test_list_input(self): npt.assert_equal(line_block.y, np.array([[5, 6, 7], [4, 2, 9]])) npt.assert_equal(line_block.x, np.array([[1, 2, 3], [1, 2, 3]])) + @pytest.mark.xfail + def test_ragged_list_input(self): + x_data = [np.array([1, 2, 3]), np.array([1, 2, 3, 4])] + y_data = [np.array([5, 6, 7]), np.array([4, 2, 9, 10])] + line_block = amp.blocks.Line(x_data, y_data) + npt.assert_equal(line_block.y, x_data) + npt.assert_equal(line_block.x, y_data) + def test_bad_input(self): # incorrect number of args with pytest.raises(ValueError) as err: From 45d64efead7d53cf14ec74259bc09492765411fc Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Mon, 4 Feb 2019 16:57:53 +0000 Subject: [PATCH 4/8] Support for ragged arrays --- animatplot/blocks/lineplots.py | 66 ++++++++++++++++++++-------------- tests/test_blocks.py | 20 +++++++++-- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/animatplot/blocks/lineplots.py b/animatplot/blocks/lineplots.py index d007cd7..fa53248 100644 --- a/animatplot/blocks/lineplots.py +++ b/animatplot/blocks/lineplots.py @@ -57,37 +57,51 @@ def __init__(self, *args, ax=None, t_axis=0, **kwargs): if y is None: raise ValueError("Must supply y data to plot") y = np.asanyarray(y) - if not all(len(l) == len(y[0]) for l in y): - raise NotImplementedError("Ragged array!") + print(str(y.dtype)) + if str(y.dtype) == 'object': + # ragged array + print("ragged array!") + if x is None: + raise ValueError("Must specify x data explicitly when passing" + "a ragged array for y data") + else: + x = np.asanyarray(x) + + if not all(len(xline) == len(yline) for xline, yline in zip(x, y)): + raise ValueError("Length of x & y data must match one another " + "for every frame") + else: + self._is_list = True else: + # Rectangular data if y.ndim != 2: raise ValueError("y data must be 2-dimensional") - # x is optional - shape = list(y.shape) - shape.remove(y.shape[t_axis]) - data_length, = shape - if x is None: - x = np.arange(data_length) - else: - x = np.asanyarray(x) - - shape_mismatch = "The dimensions of x must be compatible with those " \ - "of y, but the shape of x is {} and the shape of y " \ - "is {}".format(x.shape, y.shape) - if x.ndim == 1: - # x is constant over time - if len(x) == data_length: - # Broadcast x to match y - x = np.expand_dims(x, axis=t_axis) - x = np.repeat(x, repeats=y.shape[t_axis], axis=t_axis) + # x is optional + shape = list(y.shape) + shape.remove(y.shape[t_axis]) + data_length, = shape + if x is None: + x = np.arange(data_length) else: - raise ValueError(shape_mismatch) - elif x.ndim == 2: - if x.shape != y.shape: - raise ValueError(shape_mismatch) - else: - raise ValueError("x, must be either 1- or 2-dimensional") + x = np.asanyarray(x) + + shape_mismatch = "The dimensions of x must be compatible with " \ + "those of y, but the shape of x is {} and the " \ + "shape of y is {}".format(x.shape, y.shape) + if x.ndim == 1: + # x is constant over time + if len(x) == data_length: + # Broadcast x to match y + x = np.expand_dims(x, axis=t_axis) + x = np.repeat(x, repeats=y.shape[t_axis], axis=t_axis) + else: + raise ValueError(shape_mismatch) + elif x.ndim == 2: + if x.shape != y.shape: + raise ValueError(shape_mismatch) + else: + raise ValueError("x, must be either 1- or 2-dimensional") self.x = x self.y = y diff --git a/tests/test_blocks.py b/tests/test_blocks.py index 125f140..1a6c264 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -131,13 +131,27 @@ def test_list_input(self): npt.assert_equal(line_block.y, np.array([[5, 6, 7], [4, 2, 9]])) npt.assert_equal(line_block.x, np.array([[1, 2, 3], [1, 2, 3]])) - @pytest.mark.xfail def test_ragged_list_input(self): x_data = [np.array([1, 2, 3]), np.array([1, 2, 3, 4])] y_data = [np.array([5, 6, 7]), np.array([4, 2, 9, 10])] + + with pytest.raises(ValueError) as err: + line_block = amp.blocks.Line(y_data) + assert "Must specify x data explicitly" in str(err) + line_block = amp.blocks.Line(x_data, y_data) - npt.assert_equal(line_block.y, x_data) - npt.assert_equal(line_block.x, y_data) + print(repr(line_block.x)) + print(repr(np.array(x_data))) + npt.assert_equal(line_block.x, np.array(x_data)) + npt.assert_equal(line_block.y, np.array(y_data)) + + def test_bad_ragged_list_input(self): + x_data = np.array([np.array([1, 2, 3]), np.array([1, 2, 3, 4])]) + y_data = np.array([np.array([5, 6, 7]), np.array([4, 2, 9, 10, 11])]) + + with pytest.raises(ValueError) as err: + line_block = amp.blocks.Line(x_data, y_data) + assert "x & y data must match" in str(err) def test_bad_input(self): # incorrect number of args From da419df953b98504e060f3f07dd877ea66a11f05 Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Mon, 4 Feb 2019 16:59:10 +0000 Subject: [PATCH 5/8] xfailed test --- tests/test_blocks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_blocks.py b/tests/test_blocks.py index 1a6c264..10b57bc 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -131,6 +131,7 @@ def test_list_input(self): npt.assert_equal(line_block.y, np.array([[5, 6, 7], [4, 2, 9]])) npt.assert_equal(line_block.x, np.array([[1, 2, 3], [1, 2, 3]])) + @pytest.mark.xfail(reason="Weird assertion behaviour by numpy") def test_ragged_list_input(self): x_data = [np.array([1, 2, 3]), np.array([1, 2, 3, 4])] y_data = [np.array([5, 6, 7]), np.array([4, 2, 9, 10])] From f8883fa79184e7afd101d949478f9dafe5c171f3 Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Mon, 4 Feb 2019 18:37:56 +0000 Subject: [PATCH 6/8] Removed debugging print statements --- animatplot/blocks/lineplots.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/animatplot/blocks/lineplots.py b/animatplot/blocks/lineplots.py index fa53248..fe0b634 100644 --- a/animatplot/blocks/lineplots.py +++ b/animatplot/blocks/lineplots.py @@ -57,10 +57,8 @@ def __init__(self, *args, ax=None, t_axis=0, **kwargs): if y is None: raise ValueError("Must supply y data to plot") y = np.asanyarray(y) - print(str(y.dtype)) if str(y.dtype) == 'object': # ragged array - print("ragged array!") if x is None: raise ValueError("Must specify x data explicitly when passing" "a ragged array for y data") From 3fd2e21a10e41ccd2caf1a1dcaf1b49e7456924a Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Wed, 6 Feb 2019 17:47:20 +0000 Subject: [PATCH 7/8] Found a better way to test equality of jagged arrays --- tests/test_blocks.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/test_blocks.py b/tests/test_blocks.py index 10b57bc..358ac7f 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -71,6 +71,11 @@ def test_mpl_kwargs(self): assert actual._mpl_kwargs == expected +def assert_jagged_arrays_equal(x, y): + for x, y in zip(x, y): + npt.assert_equal(x, y) + + class TestLineBlock: def test_2d_inputs(self): x = np.linspace(0, 1, 10) @@ -131,7 +136,6 @@ def test_list_input(self): npt.assert_equal(line_block.y, np.array([[5, 6, 7], [4, 2, 9]])) npt.assert_equal(line_block.x, np.array([[1, 2, 3], [1, 2, 3]])) - @pytest.mark.xfail(reason="Weird assertion behaviour by numpy") def test_ragged_list_input(self): x_data = [np.array([1, 2, 3]), np.array([1, 2, 3, 4])] y_data = [np.array([5, 6, 7]), np.array([4, 2, 9, 10])] @@ -141,10 +145,9 @@ def test_ragged_list_input(self): assert "Must specify x data explicitly" in str(err) line_block = amp.blocks.Line(x_data, y_data) - print(repr(line_block.x)) - print(repr(np.array(x_data))) - npt.assert_equal(line_block.x, np.array(x_data)) - npt.assert_equal(line_block.y, np.array(y_data)) + + assert_jagged_arrays_equal(line_block.x, np.array(x_data)) + assert_jagged_arrays_equal(line_block.y, np.array(y_data)) def test_bad_ragged_list_input(self): x_data = np.array([np.array([1, 2, 3]), np.array([1, 2, 3, 4])]) From 88f19ebc3e144bb03767f03a38785b22ec19bda4 Mon Sep 17 00:00:00 2001 From: Thomas Nicholas Date: Thu, 7 Feb 2019 00:19:30 +0000 Subject: [PATCH 8/8] Small API and bug fixes --- animatplot/blocks/lineplots.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/animatplot/blocks/lineplots.py b/animatplot/blocks/lineplots.py index fe0b634..cb65bed 100644 --- a/animatplot/blocks/lineplots.py +++ b/animatplot/blocks/lineplots.py @@ -40,7 +40,7 @@ class Line(Block): ----- This block animates a single line - to animate multiple lines you must call this once for each line, and then animate all of the blocks returned by - passing a list of those blocks to `animatplot.animation.Animation`. + passing a list of those blocks to `animatplot.Animation`. """ def __init__(self, *args, ax=None, t_axis=0, **kwargs): @@ -58,18 +58,21 @@ def __init__(self, *args, ax=None, t_axis=0, **kwargs): raise ValueError("Must supply y data to plot") y = np.asanyarray(y) if str(y.dtype) == 'object': + self.t_axis = 0 + # ragged array if x is None: raise ValueError("Must specify x data explicitly when passing" "a ragged array for y data") - else: - x = np.asanyarray(x) + + x = np.asanyarray(x) if not all(len(xline) == len(yline) for xline, yline in zip(x, y)): raise ValueError("Length of x & y data must match one another " "for every frame") - else: - self._is_list = True + + self._is_list = True + else: # Rectangular data if y.ndim != 2: