diff options
author | Andrew Eckart <andrew.g.eckart@gmail.com> | 2020-09-11 11:31:03 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-11 09:31:03 -0700 |
commit | 3329d26c5d1d53f7fca1dfe253fdc43e93f0f6aa (patch) | |
tree | 9ddb298c4578ca268ae52a755c1dac1f99391333 | |
parent | 02798e4ce39ae7882b19a3fde209472b7411d48a (diff) | |
download | numpy-3329d26c5d1d53f7fca1dfe253fdc43e93f0f6aa.tar.gz |
ENH: Allow genfromtxt to unpack structured arrays (#16650)
* ENH: Allow genfromtxt to unpack structured arrays
genfromtxt failed to transpose output when
unpack=True and `dtype` was structured (or None).
This patch resolves the issue by
returning a list of arrays, as in `loadtxt`.
Co-authored-by: Matti Picus <matti.picus@gmail.com>
Co-authored-by: Eric Wieser <wieser.eric@gmail.com>
Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
Co-authored-by: Ross Barnowski <rossbar@berkeley.edu>
-rw-r--r-- | doc/release/upcoming_changes/16650.compatibility.rst | 16 | ||||
-rw-r--r-- | numpy/lib/npyio.py | 22 | ||||
-rw-r--r-- | numpy/lib/tests/test_io.py | 47 |
3 files changed, 79 insertions, 6 deletions
diff --git a/doc/release/upcoming_changes/16650.compatibility.rst b/doc/release/upcoming_changes/16650.compatibility.rst new file mode 100644 index 000000000..653232355 --- /dev/null +++ b/doc/release/upcoming_changes/16650.compatibility.rst @@ -0,0 +1,16 @@ +`numpy.genfromtxt` now correctly unpacks structured arrays +---------------------------------------------------------- +Previously, `numpy.genfromtxt` failed to unpack if it was called with +``unpack=True`` and a structured datatype was passed to the ``dtype`` argument +(or ``dtype=None`` was passed and a structured datatype was inferred). +For example:: + + >>> data = StringIO("21 58.0\n35 72.0") + >>> np.genfromtxt(data, dtype=None, unpack=True) + array([(21, 58.), (35, 72.)], dtype=[('f0', '<i8'), ('f1', '<f8')]) + +Structured arrays will now correctly unpack into a list of arrays, +one for each column:: + + >>> np.genfromtxt(data, dtype=None, unpack=True) + [array([21, 35]), array([58., 72.])] diff --git a/numpy/lib/npyio.py b/numpy/lib/npyio.py index c6a19fda9..90e16643c 100644 --- a/numpy/lib/npyio.py +++ b/numpy/lib/npyio.py @@ -815,8 +815,9 @@ def loadtxt(fname, dtype=float, comments='#', delimiter=None, fourth column the same way as ``usecols = (3,)`` would. unpack : bool, optional If True, the returned array is transposed, so that arguments may be - unpacked using ``x, y, z = loadtxt(...)``. When used with a structured - data-type, arrays are returned for each field. Default is False. + unpacked using ``x, y, z = loadtxt(...)``. When used with a + structured data-type, arrays are returned for each field. + Default is False. ndmin : int, optional The returned array will have at least `ndmin` dimensions. Otherwise mono-dimensional axes will be squeezed. @@ -1640,7 +1641,9 @@ def genfromtxt(fname, dtype=float, comments='#', delimiter=None, If 'lower', field names are converted to lower case. unpack : bool, optional If True, the returned array is transposed, so that arguments may be - unpacked using ``x, y, z = loadtxt(...)`` + unpacked using ``x, y, z = genfromtxt(...)``. When used with a + structured data-type, arrays are returned for each field. + Default is False. usemask : bool, optional If True, return a masked array. If False, return a regular array. @@ -2269,9 +2272,18 @@ def genfromtxt(fname, dtype=float, comments='#', delimiter=None, if usemask: output = output.view(MaskedArray) output._mask = outputmask + output = np.squeeze(output) if unpack: - return output.squeeze().T - return output.squeeze() + if names is None: + return output.T + elif len(names) == 1: + # squeeze single-name dtypes too + return output[names[0]] + else: + # For structured arrays with multiple fields, + # return an array for each field. + return [output[field] for field in names] + return output _genfromtxt_with_like = array_function_dispatch( diff --git a/numpy/lib/tests/test_io.py b/numpy/lib/tests/test_io.py index 38d698df4..aa4499764 100644 --- a/numpy/lib/tests/test_io.py +++ b/numpy/lib/tests/test_io.py @@ -1026,7 +1026,7 @@ class TestLoadTxt(LoadTxtBase): a = np.array([b'start ', b' ', b'']) assert_array_equal(x['comment'], a) - def test_structure_unpack(self): + def test_unpack_structured(self): txt = TextIO("M 21 72\nF 35 58") dt = {'names': ('a', 'b', 'c'), 'formats': ('|S1', '<i4', '<f4')} a, b, c = np.loadtxt(txt, dtype=dt, unpack=True) @@ -2358,6 +2358,51 @@ M 33 21.99 assert_equal(test['f1'], 17179869184) assert_equal(test['f2'], 1024) + def test_unpack_structured(self): + # Regression test for gh-4341 + # Unpacking should work on structured arrays + txt = TextIO("M 21 72\nF 35 58") + dt = {'names': ('a', 'b', 'c'), 'formats': ('S1', 'i4', 'f4')} + a, b, c = np.genfromtxt(txt, dtype=dt, unpack=True) + assert_equal(a.dtype, np.dtype('S1')) + assert_equal(b.dtype, np.dtype('i4')) + assert_equal(c.dtype, np.dtype('f4')) + assert_array_equal(a, np.array([b'M', b'F'])) + assert_array_equal(b, np.array([21, 35])) + assert_array_equal(c, np.array([72., 58.])) + + def test_unpack_auto_dtype(self): + # Regression test for gh-4341 + # Unpacking should work when dtype=None + txt = TextIO("M 21 72.\nF 35 58.") + expected = (np.array(["M", "F"]), np.array([21, 35]), np.array([72., 58.])) + test = np.genfromtxt(txt, dtype=None, unpack=True, encoding="utf-8") + for arr, result in zip(expected, test): + assert_array_equal(arr, result) + assert_equal(arr.dtype, result.dtype) + + def test_unpack_single_name(self): + # Regression test for gh-4341 + # Unpacking should work when structured dtype has only one field + txt = TextIO("21\n35") + dt = {'names': ('a',), 'formats': ('i4',)} + expected = np.array([21, 35], dtype=np.int32) + test = np.genfromtxt(txt, dtype=dt, unpack=True) + assert_array_equal(expected, test) + assert_equal(expected.dtype, test.dtype) + + def test_squeeze_scalar(self): + # Regression test for gh-4341 + # Unpacking a scalar should give zero-dim output, + # even if dtype is structured + txt = TextIO("1") + dt = {'names': ('a',), 'formats': ('i4',)} + expected = np.array((1,), dtype=np.int32) + test = np.genfromtxt(txt, dtype=dt, unpack=True) + assert_array_equal(expected, test) + assert_equal((), test.shape) + assert_equal(expected.dtype, test.dtype) + class TestPathUsage: # Test that pathlib.Path can be used |