1NumPy ndarray:多维数组对象
NumPy的核心特征之一就是N-维数组对象——ndarray。
首先导入NumPy,然后生成一个小的随机数组:
import numpy as np
data = np.random.randn(2,3)
print(data)
----------------------------------
[[-0.16662491 0.17114219 -0.44236483]
[ 0.06577042 0.54026812 0.62346929]]
然后可以给data加上一个数学操作:
print(data * 10)
print(data + data)
第一个输出结果中data中所有的值都同时乘了10,第二个输出结果中data中的对象元素对应相加。
一个ndarray是一个通用的多维同类数据容器,它包含的每一个元素均为相同类型。每一个数组都有一个shape属性,用来表征数组每一维度的数量,每一个数组都有一个dtype属性,用来描述数组的数据类型:
print(data.shape) #(2, 3)
print(data.dtype) #float64
1.1生成ndarray
生成数组最简单的方式就是使用array函数。array函数接收任意的序列型对象,生成一个新的包含传递数据的NumPy数组。例如,列表的转换:
data1 = [6,7.5,8,0,1]
arr1 = np.array(data1)
print(arr1) #[6. 7.5 8. 0. 1. ]
嵌套序列,例如同等长度的列表,将会自动转换成多维数组:
data2 = [[1,2,3,4],[5,6,7,8]]
arr2 = np.array(data2)
print(arr2)
----------------------------------
[[1 2 3 4]
[5 6 7 8]]
data2是一个包含列表的列表,所以NumPy数组arr2形成了二维数组。我们可以通过检查ndim和shape属性来确认这一点:
print(arr2.ndim) #2
print(arr2.shape) #(2, 4)
除了np.array,还有很多其他函数可以创建新数组。例如,给定长度及形状后,zeros可以一次性创造全0数组,ones可以一次性创造全1数组。empty则可以创建一个没有初始化数值的数组。想要创建高维数组,则可以为shape传递一个元祖:
print(np.zeros(10))
-----------------------------
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
print(np.zeros((3,6)))
-----------------------------
[[0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0.]]
print(np.empty((2,3,2)))
-----------------------------
[[[6.23042070e-307 4.67296746e-307]
[1.69121096e-306 1.86921822e-306]
[2.22518251e-306 1.60218491e-306]]
[[1.37962320e-306 1.78019354e-306]
[9.17714149e+170 1.29060558e-306]
[1.24611741e-306 1.11261027e-306]]]
arrange是python内建函数range的数组版:
print(np.arange(15)) #[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]
表1.1:数组生成函数
函数名 | 描述 |
---|---|
array | 将输入数据(可以是列表、元祖、数组以及其他序列)转换为ndarray |
asarray | 将输入转换为ndarray,但如果输入已经是ndarray则不再复制 |
arange | python内建函数range的数组版,返回一个数组 |
ones | 根据给定形状和数据类型生成全1数组 |
ones_like | 根据所给的数组生成一个形状一样的全1数组 |
zeros | 根据给定形状和数据类型生成全0数组 |
zeros_like | 根据所给的数组生成一个形状一样的全0数组 |
empty | 根据给定形状生成一个没有初始化数值的空数组 |
empty_like | 根据所给数组生成一个形状一样但没有初始化数值的空数组 |
full | 根据给定的形状和数据类型生成指定数值的数组 |
full_like | 根据所给的数组生成一个形状一样但内容是指定数值的数组 |
eye,identity | 生成一个N*N特征矩阵(对角线位置都是1,其余位置都是0) |
1.2nparray的数据类型
数据类型,即dtype,是一个特殊的对象,它包含了ndarray需要为某一种类型数据所申明的内存块信息(即表示数据的数据):
arr1 = np.array([1,2,3],dtype = np.float64)
arr2 = np.array([1,2,3],dtype = np.int32)
print(arr1.dtype) #float64
print(arr2.dtype) #int32
我们可以使用astype方法显示地转换数组的数据类型:
arr = np.array([1,2,3,4,5])
print(arr.dtype) #int32
float_arr = arr.astype(np.float64)
print(float_arr.dtype) #float64
上面的例子中,整数被转换为了浮点数,如果把浮点数转换为整数,那么小数点后的部分将被消除:
arr = np.array([3.7,3.5,1,9.2])
print(arr.dtype) #float64
int_arr = arr.astype(np.int32)
print(int_arr) #[3 3 1 9]
如果有一个数组,里面的元素都是表达数字含义的字符串,也可以通过astype将字符串转换为数字:
numeric_strings = np.array(['1.25','-9.6','43'],dtype = np.string_)
print(numeric_strings.astype(float)) #[ 1.25 -9.6 43. ]
我们也可以使用另一个数组的dtype属性:
int_array = np.arange(10)
calibers = np.array([.22,.270,.22,.16,.18],dtype = np.float64)
print(int_array.astype(calibers.dtype)) #[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
也可以使用类型代码来传入数据:
empty_uint32 = np.empty(8,dtype = 'u4')
print(empty_uint32)
------------------------------------------------
[2128575739 1309500030 1661424176 1988385690 1324770695 12290
0 805457654]
使用astype时总是生成一个新的数组,即使传入的dtype与之前的一样。
1.3NumPy数组算术
数组之所以重要是因为它允许你进行批量操作而无须任何for循环。这种特性称为向量化。任何在两个等尺寸数组之间的算术操作都应用了逐元素操作的方式:
arr = np.array([[1.,2.,3.],[4.,5.,6.]])
print(arr)
--------------------------------------------
[[1. 2. 3.]
[4. 5. 6.]]
print(arr * arr)
--------------------------------------------
[[ 1. 4. 9.]
[16. 25. 36.]]
print(arr - arr)
--------------------------------------------
[[0. 0. 0.]
[0. 0. 0.]]
带有标量计算的算术操作,也会把计算参数传递给数组的每一个元素。
同尺寸数组之间的比较,会产生一个布尔值数组:
arr = np.array([[1.,2.,3.],[4.,5.,6.]])
arr2 = np.array([[0.,4.,1.],[7.,2.,12.]])
print(arr2)
--------------------------------------------
[[ 0. 4. 1.]
[ 7. 2. 12.]]
print(arr2 > arr)
--------------------------------------------
[[False True False]
[ True False True]]
不同尺寸的数组间的操作,将会用到广播特性。
1.4基础索引与切片
区别于python的内建列表,数组的切片是原数组的视图。这意味着数据并不是被复制了,任何对于视图的修改都会反映到原数组上:
arr = np.arange(10)
arr[5:8] = 12
arr_slice = arr[5:8]
print(arr_slice) #[12 12 12]
arr_slice[1] = 22
print(arr) #[ 0 1 2 3 4 12 22 12 8 9]
不写切片值的[:]将会引用数组的所有值:
arr_slice[:] = 32
print(arr) #[ 0 1 2 3 4 32 32 32 8 9]
由于NumPy被设计成适合处理非常大的数组,所以如果NumPy持续复制数据将会引起很多的内存问题。
如果还是想要一份数组切片的拷贝而不是一份视图的话,我们就必须显示地复制这个数组,例如:arr[5:8].copy()
在多维数组中,可以省略后续索引值,返回的对象将会是降低一个维度的数组:
arr3d = np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])
print(arr3d)
--------------------------------------------
[[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
print(arr3d[0])
--------------------------------------------
[[1 2 3]
[4 5 6]]
标量和数组都可以传递给arr3d[0]:
old_values = arr3d[0].copy()
arr3d[0] = 42
print(arr3d)
--------------------------------------------
[[[42 42 42]
[42 42 42]]
[[ 7 8 9]
[10 11 12]]]
arr3d[0] = old_values
print(arr3d)
--------------------------------------------
[[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
对于二维数组的切片有所不同:
arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
print(arr2d[:2])
--------------------------------------------
[[1 2 3]
[4 5 6]]
数组沿着轴0进行了切片,表达式arrzd[:2]的含义为选择arr2d的前两行
我们也可以进行多组切片:
print(arr2d[:2,1:])
--------------------------------------------
[[2 3]
[5 6]]
当对切片表达式赋值时,整个切片都会重新赋值:
arr2d[:2,1:] = 0
print(arr2d)
--------------------------------------------
[[1 0 0]
[4 0 0]
[7 8 9]]
1.5布尔索引
我们经常会使用numpy.random中的randn函数来生成一些随机正态分布的数据:
names = np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe'])
data = np.random.randn(7,4)
print(names)
--------------------------------------------
['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']
print(data)
--------------------------------------------
[[ 1.9739665 0.9897147 -0.78550736 -0.9275028 ]
[-0.47926498 -0.83707164 -1.08190423 -2.06021371]
[ 0.11140563 0.37300882 -0.19671083 -0.57878727]
[-0.4813887 1.92794616 -0.54502391 -1.24390633]
[ 0.02961092 -0.90273863 0.44457704 0.75884028]
[ 0.25222733 1.36974475 0.23402606 0.76146159]
[ 0.57749217 -2.65068358 0.06935796 1.00334315]]
假设每个人名都和data数组中的一行对应,并且想要选中所有’Bob’对应的行。数组的比较操作也是可以向量化的。因此,比较names数组和字符串’Bob’会产生一个布尔值数组:
print(names == 'Bob') #[ True False False True False False False]
在索引数组时可以传入布尔数组值:
print(data[names == 'Bob'])
--------------------------------------------
[[ 1.9739665 0.9897147 -0.78550736 -0.9275028 ]
[-0.4813887 1.92794616 -0.54502391 -1.24390633]]
为了选择除了’Bob’以外的其他数据,我们可以选择!=或在条件表达式前使用~对条件取反:
print(names != 'Bob') #[False True True False True True True]
~符号可以在我们想要对一个通用条件进行取反时使用:
cond = names == 'Bob'
print(data[~cond])
--------------------------------------------
[[-0.47926498 -0.83707164 -1.08190423 -2.06021371]
[ 0.11140563 0.37300882 -0.19671083 -0.57878727]
[ 0.02961092 -0.90273863 0.44457704 0.75884028]
[ 0.25222733 1.36974475 0.23402606 0.76146159]
[ 0.57749217 -2.65068358 0.06935796 1.00334315]]
当要选择三个名字中的两个时,我们可以对多个布尔值条件进行联合,需要使用数学操作符&(and)和|(or):
mask = (names == 'Bob') | (names == 'Will')
print(mask) #[ True False True True True False False]
使用布尔值索引选择数据时,总是生成数据的拷贝,即使返回的数组并没有任何变化。
python的关键字and和or队布尔值数组并没有用,请使用&和|来代替。
基于常识来设置布尔值数组的值也是可行的。将data中所有的负值设置为0,我们只需要如下代码即可:
data[data < 0] = 0
利用一维布尔数组对每一行设置数组也是非常简单的:
data[names != 'Joe'] = 8
1.6神奇索引
神奇索引用于描述使用整数数组进行数据索引。
假设有一个8*4的数组:
arr = np.empty((8,4))
for i in range(8):
arr[i] = i
print(arr)
--------------------------------------------
[[0. 0. 0. 0.]
[1. 1. 1. 1.]
[2. 2. 2. 2.]
[3. 3. 3. 3.]
[4. 4. 4. 4.]
[5. 5. 5. 5.]
[6. 6. 6. 6.]
[7. 7. 7. 7.]]
为了选出一个符合特定顺序的子集,我们可以简单地通过传递一个包含指明所需顺序的列表或者数组来完成:
print(arr[[4,3,0,6]])
--------------------------------------------
[[4. 4. 4. 4.]
[3. 3. 3. 3.]
[0. 0. 0. 0.]
[6. 6. 6. 6.]]
如果使用负的索引,将从尾部进行选择:
print(arr[[-3,-5,-7]])
--------------------------------------------
[[5. 5. 5. 5.]
[3. 3. 3. 3.]
[1. 1. 1. 1.]]
传递多个索引数组时情况有些许不同,这样会根据每个索引元祖对应的元素选出一个一维数组:
arr = np.arange(32).reshape((8,4))
print(arr)
--------------------------------------------
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[16 17 18 19]
[20 21 22 23]
[24 25 26 27]
[28 29 30 31]]
print(arr[[1,5,7,2],[0,3,1,2]])
--------------------------------------------
[ 4 23 29 10]
神奇索引的行为和一些用户所设想的并不相同。通常情况下,我们所设想的结果是通过选择矩阵中行列的子集所形成的矩形区域。下面是实现我们想法的一种方式:
print(arr[[1,5,7,2]][:,[0,3,1,2]])
--------------------------------------------
[[ 4 7 5 6]
[20 23 21 22]
[28 31 29 30]
[ 8 11 9 10]]
1.7数组转置和换轴
转置是一种特殊的数据重组方式,可以返回底层数据的视图而不需要复制任何内容。数组拥有transpose方法,也有特殊的T属性。
当进行矩阵计算时,我们可能经常会进行一些特定操作,比如,当计算矩阵内积会使用np.dot:
arr = np.random.randn(6,3)
print(arr)
--------------------------------------------
[[ 0.05633563 0.47718954 1.23340357]
[-0.07206776 0.46227127 0.21771718]
[ 1.45472632 1.6935244 -0.06247615]
[-0.46176249 0.90008442 -1.02197697]
[-0.50517647 0.05549249 0.3537419 ]
[ 1.36490178 -2.3503037 -0.67012919]]
print(np.dot(arr.T,arr))
--------------------------------------------
[[ 4.45598085 -1.19440998 -0.6585435 ]
[-1.19440998 9.64658834 1.25817839]
[-0.6585435 1.25817839 3.19123179]]
`