summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Eckart <andrew.g.eckart@gmail.com>2020-09-11 11:31:03 -0500
committerGitHub <noreply@github.com>2020-09-11 09:31:03 -0700
commit3329d26c5d1d53f7fca1dfe253fdc43e93f0f6aa (patch)
tree9ddb298c4578ca268ae52a755c1dac1f99391333
parent02798e4ce39ae7882b19a3fde209472b7411d48a (diff)
downloadnumpy-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.rst16
-rw-r--r--numpy/lib/npyio.py22
-rw-r--r--numpy/lib/tests/test_io.py47
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