.dtype是用来做什么的?

19

我刚学Python,不明白.dtype是什么意思。
例如:

>>> aa
array([1, 2, 3, 4, 5, 6, 7, 8])
>>> aa.dtype = "float64"
>>> aa
array([  4.24399158e-314,   8.48798317e-314,   1.27319747e-313,
     1.69759663e-313])

我认为dtype是aa的一个属性,应该是int类型的,如果我赋值aa.dtype = "float64",那么aa就应该变成array([1.0 ,2.0 ,3.0, 4.0, 5.0, 6.0, 7.0, 8.0])

为什么会改变其值和大小?这是什么意思?

实际上,我是在学习一段代码,我需要把它粘贴在这里吗?

def to_1d(array):
 """prepares an array into a 1d real vector"""
    a = array.copy() # copy the array, to avoid changing global
    orig_dtype = a.dtype
    a.dtype = "float64" # this doubles the size of array
    orig_shape = a.shape
    return a.ravel(), (orig_dtype, orig_shape) #flatten and return

我认为它不应该改变输入数组的值,而只是改变其大小。不明白这个函数是如何工作的。


如果有疑问,阅读手册 - the wolf
我尝试理解手册,但是它的英语和计算机语言对我来说太难了,无法掌握我需要的信息。 - user1233157
1
@user1233157:这不是最优美的文字写作,是吧? - the wolf
4个回答

45

首先,你正在学习的代码有缺陷。根据代码中的注释,它几乎肯定不会执行原始作者认为它可以执行的操作。

作者可能的意思是这样的:

def to_1d(array):
    """prepares an array into a 1d real vector"""
    return array.astype(np.float64).ravel()

然而,如果array始终是由复数数组组成,则原始代码有些意义。

仅当数组类型为复数(numpy.complex128)或128位浮点数数组时,查看数组(a.dtype = 'float64'等同于a = a.view('float64'))可能会使其大小加倍。 对于任何其他dtype,这并不太合理。

对于复数数组的特定情况,原始代码将类似于np.array([0.5+1j, 9.0+1.33j])的东西转换为np.array([0.5, 1.0, 9.0, 1.33])

更简洁的写法如下:

def complex_to_iterleaved_real(array):
     """prepares a complex array into an "interleaved" 1d real vector"""
    return array.copy().view('float64').ravel()

(暂不考虑返回原始数据类型和形状的部分。)


numpy数组的背景

要解释这里发生的事情,您需要了解一些关于numpy数组的内容。

一个numpy数组由“原始”内存缓冲区组成,通过“视图”被解释为数组。你可以将所有的numpy数组都看作视图。

在numpy中,视图只是不同的方式来切片和处理同一块内存缓冲区,而无需复制。

视图具有形状、数据类型(dtype)、偏移量和步幅。在可能的情况下,对numpy数组进行索引/重塑操作将只返回原始内存缓冲区的视图。

这意味着像y = x.Ty = x[::2]这样的操作不会使用任何额外的内存,并且不会复制x

因此,如果我们有一个类似于这样的数组:

import numpy as np
x = np.array([1,2,3,4,5,6,7,8,9,10])

我们可以通过以下两种方式来重塑它:

x = x.reshape((2, 5))
或者
x.shape = (2, 5)

为了可读性,第一个选项更好。它们(几乎)完全等效。但是两者都不会产生使用更多内存的副本(第一个将导致一个新的Python对象,但就目前而言,这是无关紧要的)。


数据类型和视图

对于数据类型(dtype)也是同样的道理。我们可以通过设置x.dtype或调用x.view(...)来将一个数组视为不同的数据类型。

因此,我们可以像这样做:

import numpy as np
x = np.array([1,2,3], dtype=np.int)

print 'The original array'
print x

print '\n...Viewed as unsigned 8-bit integers (notice the length change!)'
y = x.view(np.uint8)
print y

print '\n...Doing the same thing by setting the dtype'
x.dtype = np.uint8
print x

print '\n...And we can set the dtype again and go back to the original.'
x.dtype = np.int
print x

产生以下结果:

The original array
[1 2 3]

...Viewed as unsigned 8-bit integers (notice the length change!)
[1 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0]

...Doing the same thing by setting the dtype
[1 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0]

...And we can set the dtype again and go back to the original.
[1 2 3]

请记住,这会让你对内存缓冲区的解释方式进行低级别控制。

例如:

import numpy as np
x = np.arange(10, dtype=np.int)

print 'An integer array:', x
print 'But if we view it as a float:', x.view(np.float)
print "...It's probably not what we expected..."

这将得到:

An integer array: [0 1 2 3 4 5 6 7 8 9]
But if we view it as a float: [  0.00000000e+000   4.94065646e-324   
   9.88131292e-324   1.48219694e-323   1.97626258e-323   
   2.47032823e-323   2.96439388e-323   3.45845952e-323
   3.95252517e-323   4.44659081e-323]
...It's probably not what we expected...

因此,在这种情况下,我们将原始内存缓冲区的底层位解释为浮点数。

如果我们想要制作一个新副本,其中整数被重新分配为浮点数,我们将使用x.astype(np.float)。


复数

复数(在C、Python和NumPy中)是作为两个浮点数存储的。第一个是实部,第二个是虚部。

所以,如果我们执行:

import numpy as np
x = np.array([0.5+1j, 1.0+2j, 3.0+0j])

我们可以看到复数的实部(x.real)和虚部(x.imag)。如果我们将其转换为浮点数,会收到关于舍弃虚部的警告,并得到只有实部的数组。

print x.real
print x.astype(float)

astype会复制并将数值转换为新类型。

但是,如果我们将此数组视为浮点数,则会得到一个序列item1.real, item1.imag, item2.real, item2.imag, ...

print x
print x.view(float)
产生:
[ 0.5+1.j  1.0+2.j  3.0+0.j]
[ 0.5  1.   1.   2.   3.   0. ]

每个复数实际上是两个浮点数,因此如果我们改变NumPy解释底层内存缓冲的方式,就可以得到长度为两倍的数组。

希望这有助于澄清问题...


哎呀,我已经花了好几个小时来看他的代码了。天啊!谢谢你指出来,我可以问一下吗,除了 dtype 部分以外,他的代码还是对的吗?我是说 reshape 部分,谢谢。 - user1233157
如果你想要返回一个数组的副本作为一维向量,只需调用 array.flatten()。然而,请查看我的更新答案... 如果 array 总是一个复杂的数组,那么这段代码确实有些意义,但是它的文档描述是具有误导性的。 - Joe Kington
非常感谢!输入数组确实是一个复杂数组,现在我明白了!哈哈,开心。 - user1233157
@Joe 你有Numpy文档的好推荐吗?例如,这个链接几乎没什么用:http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.dtype.html#numpy.ndarray.dtype。应该有好的文档资源,我个人觉得会非常有用。+1 - David Heffernan
@JoeKington,再次感谢您提供的额外信息,我从您所写的内容中学到了很多,非常全面,我非常感激! - user1233157
显示剩余2条评论

6

通过以这种方式更改数据类型,您正在更改一个固定内存块被解释的方式。

示例:

>>> import numpy as np
>>> a=np.array([1,0,0,0,0,0,0,0],dtype='int8')
>>> a
array([1, 0, 0, 0, 0, 0, 0, 0], dtype=int8)
>>> a.dtype='int64'
>>> a
array([1])

请注意,从int8int64的更改将一个8个元素、8位整数数组变成了一个1个元素、64位数组。然而,它仍是相同的8字节块。在我的本地端 i7 机器上,字节模式与 int64 格式中的 1 相同。
改变1的位置:
>>> a=np.array([0,0,0,1,0,0,0,0],dtype='int8')
>>> a.dtype='int64'
>>> a
array([16777216])

另一个例子:
>>> a=np.array([0,0,0,0,0,0,1,0],dtype='int32')
>>> a.dtype='int64'
>>> a
array([0, 0, 0, 1])

将32字节,32位数组中的1的位置更改:

>>> a=np.array([0,0,0,1,0,0,0,0],dtype='int32')
>>> a.dtype='int64'
>>> a
array([         0, 4294967296,          0,          0]) 

这是同一块位的不同解释。

尽管我已经选了Joe,但是你的答案也很好,示例非常不错!谢谢。 - user1233157
@user1233157:他的回答值得肯定!不过你还是可以给我投一票 :-) - the wolf
顺便说一句,回答很好!(+1) - Joe Kington

3
在尝试过后,我认为手动分配dtype会执行重新解释转换而不是您想要的操作。这意味着我认为它直接将数据解释为浮点数,而不是将其转换为浮点数。也许您可以尝试aa = numpy.array(aa.map(float, aa))
进一步解释:dtype是数据类型。引用文档的原话:
数据类型对象(numpy.dtype类的实例)描述了应如何解释与数组项对应的固定大小内存块中的字节。
整数和浮点数没有相同的位模式,这意味着您不能只查看整数的内存并且当您将其视为浮点数时它就是相同的数字。通过将dtype设置为float64,您只是告诉计算机将该内存读取为float64,而不是实际上将整数数字转换为浮点数。

非常感谢您回复,我实际上正在学习一段代码,它应该将一个数组转换为一维向量,但我很困惑为什么它改变了值。 - user1233157
我已经更新了我的问题,并粘贴了我所参考的代码。谢谢:) - user1233157

2

ndarraydtype属性文档并不是很有用。从你的输出来看,似乎将八个4字节整数的缓冲区重新解释为四个8字节浮点数。

但是你想要的是在创建数组时指定dtype

array([1, 2, 3, 4, 5, 6, 7, 8], dtype="float64")

dtype 的文档在这里:http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html#arrays-dtypes,如果有价值的话。你链接的甚至不是 dtype 类的文档(它在这里:http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html#numpy.dtype),它只是指向其他文档的 ndarray.dtype 的文档。 - Joe Kington
我已经更新了我的问题,请您看一下好吗?非常感谢 :) - user1233157
好的,我已经回答了原始问题,而且你接受了一个非常全面的回答,覆盖了所有的基础知识。我认为我们在这里的工作已经完成了! - David Heffernan

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