在NumPy 1.14中将结构化数组的切片转换为常规NumPy数组

3
注意1:在我的情况下,这个问题的所有答案都不适用。
注意2:解决方案必须适用于NumPy 1.14。
假设我有以下结构化数组: arr = np.array([(105.0, 34.0, 145.0, 217.0)], dtype=[('a', 'f4'), ('b', 'f4'), ('c', 'f4'), ('d', 'f4')])
现在我正在对结构化数据类型进行切片,如下所示: arr2 = arr[['a', 'b']] 现在我正在尝试将该切片转换为常规数组: out = arr2[0].view((np.float32, 2)) 这会导致 ValueError: Changing the dtype of a 0d array is only supported if the itemsize is unchanged 我想要的只是一个普通的数组,如下所示: [105.0, 34.0] 请注意,此示例经过简化,以使其最小化。在我的实际用例中,我显然不处理包含一个元素的数组。
我知道这个解决方案可行: out = np.asarray(list(arr2[0])) 但我认为必须有一种比将已经在NumPy数组中的数据复制到列表中,然后再复制回数组更有效的方法。我假设有一种方法可以保持在NumPy中,并且可能实际上根本不复制任何数据,但我不知道如何做到这一点。

1
你在寻找 np.array(arr[0].tolist()) 吗? - pault
2
我无法重现你的错误。arr[0].view((np.float32, len(arr.dtype.names))) 对我来说是有效的。 - pault
@pault 你用的是哪个Numpy版本?1.14版本对于结构化数组的一些行为进行了更改,而我正在使用这个版本。 - Alex
@Alex 可能是- 我正在使用 1.11.2 - pault
@Alex 我无法重现你的错误。我正在使用 NumPy 1.14.0 版本。 - Autonomous
显示剩余4条评论
1个回答

3

1维数组可以使用 view 进行转换:

In [270]: arr = np.array([(105.0, 34.0, 145.0, 217.0)], dtype=[('a', 'f4'), ('b','f4'), ('c', 'f4'), ('d', 'f4')])
In [271]: arr
Out[271]: 
array([(105., 34., 145., 217.)],
      dtype=[('a', '<f4'), ('b', '<f4'), ('c', '<f4'), ('d', '<f4')])
In [272]: arr.view('<f4')
Out[272]: array([105.,  34., 145., 217.], dtype=float32)

当我们尝试转换单个元素时,就会出现这个错误:
In [273]: arr[0].view('<f4')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-273-70fbab8f61ba> in <module>()
----> 1 arr[0].view('<f4')

ValueError: Changing the dtype of a 0d array is only supported if the itemsize is unchanged

之前的 view 经常需要进行尺寸调整。我怀疑最近对结构化数组的处理方式发生了变化(在同时索引多个字段时最为明显),这个错误是有意或无意地造成的结果。

在整个数组的情况下,它将 1d、4 字段数组更改为 1d、4 元素数组,(1,) 变为 (4,)。但是更改元素,则从 () 变为 (4,)。

过去,我建议使用 tolist 作为解决 view(和 astype)问题的最可靠方法:

In [274]: arr[0].tolist()
Out[274]: (105.0, 34.0, 145.0, 217.0)
In [279]: list(arr[0].tolist())
Out[279]: [105.0, 34.0, 145.0, 217.0]
In [280]: np.array(arr[0].tolist())
Out[280]: array([105.,  34., 145., 217.])

item也是从numpy结构中提取元素的好方法:

In [281]: arr[0].item()
Out[281]: (105.0, 34.0, 145.0, 217.0)
< p > tolostitem 的结果是一个元组。

你担心速度。但是你只转换了一个元素。当在1000项数组上使用tolist时担心速度是一回事,而在处理1个元素时则完全不同。

In [283]: timeit arr[0]
131 ns ± 1.31 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [284]: timeit arr[0].tolist()
1.25 µs ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [285]: timeit arr[0].item()
1.27 µs ± 2.39 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [286]: timeit arr.tolist()
493 ns ± 17.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [287]: timeit arr.view('f4')
1.74 µs ± 18.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

你可以以不降低维度为代价的方式对元素进行索引(虽然这并没有太大帮助):
In [288]: arr[[0]].view('f4')
Out[288]: array([105.,  34., 145., 217.], dtype=float32)
In [289]: timeit arr[[0]].view('f4')
6.54 µs ± 15.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [290]: timeit arr[0:1].view('f4')
2.63 µs ± 105 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [298]: timeit arr[0][None].view('f4')
4.28 µs ± 160 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

view 仍需要改变形状;考虑一个大数组:

In [299]: arrs = np.repeat(arr, 10000)
In [301]: arrs.view('f4')
Out[301]: array([105.,  34., 145., ...,  34., 145., 217.], dtype=float32)
In [303]: arrs.shape
Out[303]: (10000,)
In [304]: arrs.view('f4').shape
Out[304]: (40000,)

视图仍然是一维的,而我们可能希望一个 (10000,4) 形状的二维数组。
更好的视图更改:
In [306]: arrs.view(('f4',4))
Out[306]: 
array([[105.,  34., 145., 217.],
       [105.,  34., 145., 217.],
       [105.,  34., 145., 217.],
       ...,
       [105.,  34., 145., 217.],
       [105.,  34., 145., 217.],
       [105.,  34., 145., 217.]], dtype=float32)
In [307]: _.shape
Out[307]: (10000, 4)

这适用于包含1个元素的数组,无论是一维还是零维:
In [308]: arr.view(('f4',4))
Out[308]: array([[105.,  34., 145., 217.]], dtype=float32)
In [309]: _.shape
Out[309]: (1, 4)
In [310]: arr[0].view(('f4',4))
Out[310]: array([105.,  34., 145., 217.], dtype=float32)
In [311]: _.shape
Out[311]: (4,)

这是你链接中的一个回答提出的建议:https://dev59.com/LW025IYBdhLWcg3whGax#10171321 与你的评论相反,它对我有效:
In [312]: arr[0].view((np.float32, len(arr.dtype.names)))
Out[312]: array([105.,  34., 145., 217.], dtype=float32)
In [313]: np.__version__
Out[313]: '1.14.0'

有编辑:

In [84]: arr = np.array([(105.0, 34.0, 145.0, 217.0)], dtype=[('a', 'f4'), ('b','f4'), ('c', 'f4'), ('d', 'f4')])
In [85]: arr2 = arr[['a', 'b']]
In [86]: arr2
Out[86]: 
array([(105., 34.)],
      dtype={'names':['a','b'], 'formats':['<f4','<f4'], 'offsets':[0,4], 'itemsize':16})

In [87]: arr2.view(('f4',2))
...
ValueError: Changing the dtype to a subarray type is only supported if the total itemsize is unchanged

请注意,arr2dtype包括一个offsets值。在最近的numpy版本中,多字段选择已经改变。现在它是一个真正的视图,保留了原始数据 - 所有的数据,而不仅仅是选定的字段。项目大小未更改:
In [93]: arr.itemsize
Out[93]: 16
In [94]: arr2.itemsize
Out[94]: 16

arr.view(('f4',4))arr2.view(('f4',4)) 产生相同的结果。

因此,您无法对字段的部分集进行 view(更改数据类型)。您必须先对整个数组进行 view,然后选择行/列或使用 tolist

我正在使用 1.14.01.14.1 的发布说明如下:

在结构化数组的多字段索引中,1.14.0 中返回视图而不是副本的更改已被撤销,但仍将在 NumPy 1.15 上保持。受影响的用户应阅读 1.14.1 Numpy 用户指南中的“基础知识 / 结构化数组 / 访问多个字段”一节,以获取有关如何管理此转换的建议。

https://docs.scipy.org/doc/numpy-1.14.2/user/basics.rec.html#accessing-multiple-fields

这还在开发中。那份文档提到了一个 repack_fields 函数,但是它目前还不存在。


非常感谢您的回答!我意识到我没有正确表述我的问题。你是对的,我的 OP 中的解决方案确实在我的示例中不会导致错误。我错误地提出了这个例子。非常抱歉!我用稍微修改过的例子更新了它。现在它既代表了我的实际用例,但不幸的是肯定会引发错误 :/ - Alex
由于最近对多字段选择进行了更改,您无法这样做。 - hpaulj
非常感谢您编辑的答案,也感谢您指向相关文档!如果我应该更仔细地查看,我想现在我会转换为列表,然后看看NumPy 1.15带来了什么。 - Alex
1
版本1.17实现了新的多字段视图方法。请阅读文档和发布说明。 - hpaulj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接