你可能已经注意到, NumPy中,尤其是在做数组运算或数组操作时,返回结果不是数组的副本就是视图。 NumPy中,所有赋值运算不会为数组和数组中的任何元素创建副本
>>> a=np.array([1,2,3,4])
>>> b=a
>>> b
array([1, 2, 3, 4])
>>> a[2]=0
>>> b
array([1, 2, 0, 4])
>>>
把数组a赋给数组b,实际上不是为a创建副本,b只不过是调用数组a的另外一种方式。
事实上,修改a的第三个元素,同样会修改b的第三个元素。
数组切片操作返回的对象只是原数组的视图
>>> c=a[0:2]
>>> c
array([1, 2])
>>> a[0]=0
>>> c
array([0, 2])
>>>
如上所见,即使是切片操作得到的结果,实际上仍指向相同的对象。
如果想为原数组生成一份完整的副本,从而得到一个不同的数组,使用copy()函数即可。
>>> a=np.array([1,2,3,4])
>>> c=a.copy()
>>> c
array([1, 2, 3, 4])
>>> a[0]=0
>>> c
array([1, 2, 3, 4])
>>>
上面的例子中,即使改动数组a的元素,数组c仍保持不变。
向量化和广播这两个概念是 NumPy内部实现的基础。有了向量化,编写代码时无需使用显式循环。这些循环实际上不能省略,只不过是在内部实现,被代码中的其他结构代替。向量化的应用使得代码更简洁,可读性更强,你可以说使用了向量化方法的代码看上去更“ Pythonic".向量化使得很多运算看上去更像是数学表达式,例如, NumPy中两个数组相乘可以表示为:
a×b
甚至两个矩阵相乘也可以这么表示
A×B
其他语言的上述运算要用到多重for结构。
例如,计算数组相乘:
for(i=0;i<rows;i++){
c[i]=a[i]*b[i];
}
计算矩阵相乘
for(i=0;i<rows;i++){
for(j=0;j<columns;j++){
c[i][j]=a[i][j]*b[i][j]
}
}
由上可见,使用 NumPy时,代码的可读性更强,其表达式更像是数学表达式。
广播机制这一操作实现了对两个或以上数组进行运算或用函数处理,即使这些数组形状并不完全相同。
并不是所有的维度都要彼此兼容才符合广播机制的要求,但它们必须要满足一定的条件。
前面讲过,在 NumPy中,如何通过用表示数组各维度长度的元组(也就是数组的型)把数组
转换成多维数组。
因此,若两个数组的各维度兼容,也就是两个数组的每一维等长,或其中一个数组为一维那么广播机制就适用。如果这两个条件都不能满足, NumPy就会抛出异常,说两个数组不兼容。
>>> A=np.arange(16).reshape(4,4)
>>> b=np.arange(4)
>>> A
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
>>> b
array([0, 1, 2, 3])
>>>
执行完上述代码后。我们就得到两个数组:
4×4
4
广播机制有两条规则。
第一条是为缺失的维度补上个1.如果这时满足兼容性条件,就可以应用广播机制,再来看第二条规则。
4×4
4×1
兼容性规则满足之后,再来看一下广播机制的第二条规则。这一规则解释的是如何扩展最小的数组,使得它跟最大的数组大小相同,以便使用元素级的函数或运算符。
第二条规则假定缺失元素(一维)都用已有值进行了填充
既然两个数组维度相同,.它们里面的值就可以相加。
>>> A+b
array([[ 0, 2, 4, 6],
[ 4, 6, 8, 10],
[ 8, 10, 12, 14],
[12, 14, 16, 18]])
>>>
上例这种情况比较简单,一个数组较另一个小。还有更复杂的情况,即两个数组形状不同维度不同、互有长短。
>>> m=np.arange(6).reshape(3,1,2)
>>> n=np.arange(6).reshape(3,2,1)
>>> m
array([[[0, 1]],
[[2, 3]],
[[4, 5]]])
>>> n
array([[[0],
[1]],
[[2],
[3]],
[[4],
[5]]])
>>>
即使是这种复杂情况,分析两个数组的形状,你也会发现它们相互兼容,因此广播规则仍然适用。
3×1×2
3×2×1
这种情况下,两个数组都要扩展维度(进行广播)
然后,就可以对两个数组进行诸如加法这样的元素级运算。
>>> m+n
array([[[ 0, 1],
[ 1, 2]],
[[ 4, 5],
[ 5, 6]],
[[ 8, 9],
[ 9, 10]]])
>>>
通过前面几节的多个例子,我们讲了一维数组和二维数组。在 NumPy中,不仅可以创建规模更为复杂的数组,还可以创建结构更为复杂的数组,后者叫作结构化数组( structured array),它包含的是结构或记录而不是独立的元素。
例如,你可以创建一个简单的结构化数组,其中元素为结构体。你可以用 dtype选项,指定系列用逗号隔开的说明符,指明组成结构体的元素及它们的数据类型和顺序。
例如,你想指定由一个整数、一个长度为6的字符串和一个布尔值组成的结构体,就要在 dtype选项中按顺序指定各自的说明符。
>>> structured=np.array([(1,'first',0.5,1+2j),(2,'second',1.3,2-2j),(3,'thied',0.8,1+3j)],dtype=('i2,a6,f4,c8'))
>>> structured
array([(1, b'first', 0.5, 1.+2.j), (2, b'second', 1.3, 2.-2.j),
(3, b'thied', 0.8, 1.+3.j)],
dtype=[('f0', '), ('f1', 'S6'), ('f2', '), ('f3', ')])
>>>
你还可以在数据类型( dtype)选项中明确指定每个元素的类型,如int8、uint8、f1oat16、complex64等。
>>> structured=np.array([(1,'first',0.5,1+2j),(2,'second',1.3,2-2j),(3,'thied',0.8,1+3j)],dtype=('int16,a6,float32,complex64'))
>>> structured
array([(1, b'first', 0.5, 1.+2.j), (2, b'second', 1.3, 2.-2.j),
(3, b'thied', 0.8, 1.+3.j)],
dtype=[('f0', '), ('f1', 'S6'), ('f2', '), ('f3', ')])
>>>
然而,上述两种做法结果相同。生成的数组中, dtype序列包含结构体各项的名称及相应的数据类型。
使用索引值,就能获取到包含相应结构体的行。
>>> structured[1]
(2, b'second', 1.3, 2.-2.j)
>>>
自动赋给结构体每个元素的名称可以看成数组列的名称。用它们作为结构化索引,就能引用类型相同或是位于同列的元素。
>>> structured['f1']
array([b'first', b'second', b'thied'], dtype='|S6')
>>>
如上所见,自动分配的名称的第一个字符为f( field,字段),后面紧跟的是表示它在序列中位置的整数。其实,用更有意义的内容作为名字,用处更大。在创建数组时,可以指定各字段的名称
>>> structured=np.array([(1,'first',0.5,1+2j),(2,'second',1.3,2-2j),(3,'thied',0.8,1+3j)],dtype=[('id','i2'),('positiom','a6'),('value','f4'),('complex','c8')])
>>> structured
array([(1, b'first', 0.5, 1.+2.j), (2, b'second', 1.3, 2.-2.j),
(3, b'thied', 0.8, 1.+3.j)],
dtype=[('id', '), ('positiom', 'S6'), ('value', '), ('complex', ')])
>>>
或在创建完成后,重新定义结构化数组的 dtype属性,在元组中指定各字段的名称。
>>> structured.dtype.names=('id','order','value','complex')
>>>
现在,你可以使用更有意义的字段名来获取数组的某一列。
>>> structured['order']
array([b'first', b'second', b'thied'], dtype='|S6')