编辑推荐: |
本文来源csdn,本文主要介绍花式索引和布尔型索引涉及到复制操作,其他的都是返回源数据的视图。 |
|
NumPy(Numerical Python的基础)是高性能科学计算和数据分析的基础包。其部分功能如下:
1.ndarray,一个具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组。
2.用于对数组数据进行快速运算的标准数学函数(无序编写循环)。
3.用于读写磁盘数据的工具及其用于操作内存映射文件的工具。
4.线性代数、随机数生成以及傅里叶变换功能。
5.用于集成由C、C++、Fortran等语言编写的代码的工具。
创建ndarray
创建数组最简单的办法就是使用array函数。它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。
列表的转换:
data1
= [6,7.5,8,0,1]
arr1 = np.array(data1)
# array([ 6. , 7.5, 8. , 0. , 1. ]) |
嵌套序列(比如由一组等长列表组成的列表)将会被转为一个多维数组:
data2
= [[1,2,3,4],[5,6,7,8]]
arr2 = np.array(data2)
# array([[1, 2, 3, 4],
# [5, 6, 7, 8]]) |
data2是一个list of lists, 所以arr2维度为2。我们能用ndim和shape属性来确认一下:
除非主动声明,否则np.array会自动给data搭配适合的类型,并保存在dtype里:
arr1.dtype
# dtype('float64') |
arr2.dtype
# dtype('int64') |
除了np.array,还有一些其他函数能创建数组。比如zeros,ones,另外还可以在一个tuple里指定shape:
np.zeros(10)
# array([ 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0.]) |
np.zeros((3,6))
# array([[ 0., 0., 0., 0., 0., 0.],
# [ 0., 0., 0., 0., 0., 0.],
# [ 0., 0., 0., 0., 0., 0.]]) |
np.empty((2,3,2))
# array([[[ 0.00000000e+000, 0.00000000e+000],
# [ 2.16538378e-314, 2.16514681e-314],
# [ 2.16511832e-314, 2.16072529e-314]],
# [[ 0.00000000e+000, 0.00000000e+000],
# [ 2.14037397e-314, 6.36598737e-311],
# [ 0.00000000e+000, 0.00000000e+000]]])
|
np.empty并不能保证返回所有是0的数组,某些情况下,会返回为初始化的垃圾数值,如上。
arange是一个数组版的python range函数:
np.arange(15)
# array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14]) |
一些创建数组的函数:
ndarray的数据类型
dtype保存数据的类型:
arr1
= np.array([1, 2, 3], dtype=np.float64)
arr2 = np.array([1, 2, 3], dtype=np.int32)
arr1.dtype
# dtype('float64')
arr2.dtype
# dtype('int32')
|
dtype才是numpy能灵活处理其他外界数据的原因。
可以用astype来转换类型:
arr
= np.array([1, 2, 3, 4, 5])
arr.dtype
# dtype('int64')
float_arr = arr.astype(np.float64)
float_arr.dtype
# dtype('float64') |
上面是把int变为float。如果是把float变为int,小数点后的部分会被丢弃:
arr
= np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr
# array([ 3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
arr.astype(np.int32)
# array([ 3, -1, -2, 0, 12, 10], dtype=int32
|
还可以用astype把string里的数字变为实际的数字:
numeric_strings
= np.array(['1.25', '-9.6', '42'], dtype=np.string_)
numeric_strings
# array([b'1.25', b'-9.6', b'42'],
# dtype='|S4')
numeric_strings.astype(float)
# array([ 1.25, -9.6 , 42. ])
|
要十分注意numpy.string_类型,这种类型的长度是固定的,所以可能会直接截取部分输入而不给警告。
如果转换(casting)失败的话,会给出一个ValueError提示。
可以用其他数组的dtype直接来制定类型:
int_array
= np.arange(10)
calibers = np.array([.22, .270, .357, .380,
.44, .50], dtype=np.float64)
int_array.astype(calibers.dtype)
# array([ 0., 1., 2., 3., 4., 5., 6., 7., 8.,
9.])
|
还可以利用类型的缩写,比如u4就代表unit32:
empty_unit32
= np.empty(8, dtype='u4')
empty_unit32
# array([0, 0, 0, 0, 0, 0, 0, 0], dtype=uint32) |
astype总是会返回一个新的数组。
数组和标量之间的运算
数组很重要,因为它使你不用编写循环即可对数据执行批量运算。叫做矢量化。大小相等的数组之间的任何算术运算都会将运算应用到元素级:
arr
= np.array([[1., 2., 3.], [4., 5., 6.]])
arr
# array([[ 1., 2., 3.],
# [ 4., 5., 6.]]
arr * arr
# array([[ 1., 4., 9.],
# [ 16., 25., 36.]])
arr - arr
# array([[ 0., 0., 0.],
# [ 0., 0., 0.]])
|
数组与标量的算术运算也会将那个标量值传播到各个元素:
1
/ arr
# array([[ 1. , 0.5 , 0.33333333],
# [ 0.25 , 0.2 , 0.16666667]])
arr ** 0.5
# array([[ 1. , 1.41421356, 1.73205081],
# [ 2. , 2.23606798, 2.44948974]])
|
不同大小的数组之间的运算叫做广播(broadcasting)。
基本的索引和切片
NumPy数组的索引选取数据子集或单个元素的方式有很多。一维数组和Python列表功能差不多:
arr
= np.arange(10)
arr
# array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
arr[5]
# 5
arr[5:8]
# array([5, 6, 7])
arr[5:8] = 12
arr
# array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
|
当你将一个标量值赋给一个切片时(如arr[5:8]=12),该值会自动传播(“广播”)到整个选区。和Python列表不同的是,数组切片是原始数组的视图。这意味着数据不会被复制,任何修改都会反应到源数组上。
arr_slice
= arr[5:8]
arr_slice
# array([12, 12, 12])
arr_slice[1] = 12345
# array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9])
arr_slice[:] = 64
arr
# array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
|
若想得到ndarray切片的一份副本,就需要显式地进行复制操作,例如arr[5:8].copy()
在一个二维数组中,各索引位置上的元素不再是标量而是一维数组:
arr2d
= np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2]
# array([7, 8, 9]) |
有两种方式可以访问单一元素:
arr2d[0][2]
# 3
arr2d[0, 2]
# 3 |
二维数组的索引方式:
对于多维数组,如果省略后面的索引,返回的将是一个低纬度的多维数组。例如,一个2
x 2 x 3数组
arr3d
= np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8,
9], [10, 11, 12]]])
arr3d
# array([[[ 1, 2, 3],
# [ 4, 5, 6]],
# [[ 7, 8, 9],
# [10, 11, 12]]])
|
arr3d[0]是一个2x3数组:
arr3d[0]
# array([[1, 2, 3],
# [4, 5, 6]]) |
标量和数组都能赋给arr3d[0]:
old_values
= arr3d[0].copy()
arr3d[0] = 42
arr3d
# array([[[42, 42, 42],
# [42, 42, 42]],
# [[ 7, 8, 9],
# [10, 11, 12]]])
arr3d[0] = old_values
arr3d
# array([[[ 1, 2, 3],
# [ 4, 5, 6]],
# [[ 7, 8, 9],
# [10, 11, 12]]])
|
arr3d[1, 0]会给返回一个(1, 0)的一维数组:
arr3d[1,
0]
# array([7, 8, 9]) |
注意,上述选取数组子集的例子返回的都是视图(不是副本,是本尊)。
切片索引
ndarray的切片语法和Python列表这样的一维对象差不多。
高维对象可以再一个或多个轴上进行切片,也可以跟整数索引混合使用。
arr2d
# array([[1,2,3],
# [4,5,6]
# [7,8,9]])
arr2d[:2]
# array([[1,2,3],
# [4,5,6]]) |
可以看出,它是沿着axis 0(行)来处理的。可以一次传入多个切片,就像传入多个索引那样:
arr2d[:2,
1:]
# array([[2, 3],
# [5, 6]]) |
如此切片,只能得到相同维数的数组视图。。将整数索引和切片混合,可以得到低纬度切片:
arr2d[1,
:2]
# array([4, 5]) |
注意,只有冒号表示选取整个轴,例如:
arr2d[:,
:1]
# array([[1],
# [4],
# [7]]) |
对切片表达式的赋值操作也会扩散到整个选区:
arr2d[:2,
1:] = 0
arr2d
# array([[1, 0, 0],
# [4, 0, 0],
# [7, 8, 9]]) |
布尔型索引
假设我们有一个用于存储数据的数组以及一个存储姓名的数组(含有重复项)。比如说:
names
= np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will',
'Joe', 'Joe'])
names
# array(['Bob', 'Joe', 'Will', 'Bob', 'Will',
'Joe', 'Joe'],
# dtype='<U4')
data = np.random.randn(7, 4)
data
# array([[ 0.06226591, -0.27507719, 0.39229467,
1.0592541 ],
# [ 0.29856009, -0.287806 , -1.06875432, -0.33292789],
# [-0.48500348, -0.10072345, -1.76972263, -0.27355081],
# [ 0.23004649, -0.76163183, 0.24673954, -0.47700137],
# [ 1.54353606, -0.17964118, -0.7093982 , -1.55488714],
# [ 0.17778785, 1.25049472, 1.92926838, 0.49794146],
# [ 0.11571349, -1.28075539, -1.15407468, 0.86778147]])
|
假设每个名字对应data数组中的一行,我们想要选出对应于名字'Bob'的所有行。和算术运算一样,数组的比较运算(如==)也是矢量化的。因此,对names和字符串“Bob”的比较运算会产生一个布尔型数组:
names
== 'Bob'
# array([ True, False, False, True, False,
False, False], dtype=bool) |
布尔型数组可用于数组索引:
data[names
== 'Bob']
# array([[ 0.02584271, -1.53529621, 0.73143988,
-0.34086189],
# [-0.48632936, 0.63817756, -0.40792716, -1.48037389]])
|
布尔型数组的长度必须跟被索引的轴长度一致。还可以将布尔型数组跟切片、整数(或整数序列)混合使用:
data[names
== 'Bob', 2:]
# array([[ 0.73143988, -0.34086189],
# [-0.40792716, -1.48037389]])
data[names == 'Bob', 3]
# array([-0.34086189, -1.48037389])
|
选中除了'Bob'外的所有行,可以用!=或者~:
names
!= 'Bob'
# array([False, True, True, False, True, True,
True], dtype=bool)
data[~(names == 'Bob')]
# array([[ 0.40864782, 0.53476799, 1.09620596,
0.4846564 ],
# [ 1.95024076, -0.37291038, -0.40424703, 0.30297059],
# [-0.81976335, -1.10162466, -0.59823212, -0.10926744],
# [-0.5212113 , 0.29449179, 2.0568032 , 2.00515735],
# [-2.36066876, -0.3294302 , -0.24464646, -0.81432884]])
|
选取这三个名字中的两个需要组合应用多个布尔条件,如&(与)、|(或)之类的布尔运算符。
names
# array(['Bob', 'Joe', 'Will', 'Bob', 'Will',
'Joe', 'Joe'],
# dtype='<U4')
mask = (names == 'Bob') | (names == 'Will')
mask
# array([ True, False, True, True, True, False,
False], dtype=bool)
data[mask]
# array([[ 0.02584271, -1.53529621, 0.73143988,
-0.34086189],
# [ 1.95024076, -0.37291038, -0.40424703, 0.30297059],
# [-0.48632936, 0.63817756, -0.40792716, -1.48037389],
# [-0.81976335, -1.10162466, -0.59823212, -0.10926744]])
|
通过布尔型索引选取数组中的数据,总是创建数据的副本,即使返回一摸一样的数组也是如此。
通过布尔型数组设置值是一种常用手段,为了将data中的所有负值设置为0,只需:
data[data
< 0] = 0
data
# array([[ 0.02584271, 0. , 0.73143988, 0. ],
# [ 0.40864782, 0.53476799, 1.09620596, 0.4846564
],
# [ 1.95024076, 0. , 0. , 0.30297059],
# [ 0. , 0.63817756, 0. , 0. ],
# [ 0. , 0. , 0. , 0. ],
# [ 0. , 0.29449179, 2.0568032 , 2.00515735],
# [ 0. , 0. , 0. , 0. ]])
|
通过一维布尔数组设置整行或列的值也很简单:
data[names
!= 'Joe'] = 7
data
# array([[ 7. , 7. , 7. , 7. ],
# [ 0.40864782, 0.53476799, 1.09620596, 0.4846564
],
# [ 7. , 7. , 7. , 7. ],
# [ 7. , 7. , 7. , 7. ],
# [ 7. , 7. , 7. , 7. ],
# [ 0. , 0.29449179, 2.0568032 , 2.00515735],
# [ 0. , 0. , 0. , 0. ]])
|
花式索引
花式索引是一个NumPy术语,它指的是利用整数数组进行索引。假设有一个8 x 4的数组:
arr
= np.empty((8, 4))
for i in range(8):
arr[i] = i
arr
# array([[ 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.]])
|
为了以特定顺序选取子集,只需传入一个指定顺序的整数列表或ndarray即可:
arr[[4,
3, 0, 6]]
# array([[ 4., 4., 4., 4.],
# [ 3., 3., 3., 3.],
# [ 0., 0., 0., 0.],
# [ 6., 6., 6., 6.]])
|
使用负数索引将从末尾开始选行:
arr[[-3,
-5, -7]]
# array([[ 5., 5., 5., 5.],
# [ 3., 3., 3., 3.],
# [ 1., 1., 1., 1.]]) |
一次掺入多个索引数组会有一点特别。其返回的是一个一维数组,其中的元素对应各个索引元组:
arr
= np.arange(32).reshape((8, 4))
arr
# array([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19],
# [20, 21, 22, 23],
3 [24, 25, 26, 27],
# [28, 29, 30, 31]])
arr[[1, 5, 7, 2], [0, 3, 1, 2]]
# array([ 4, 23, 29, 10])
|
以看到[ 4, 23, 29, 10]分别对应(1, 0), (5, 3), (7, 1), (2,
2)。不论数组有多少维,花式索引的结果总是一维。选取矩阵的行列子集可以使用如下方式:
arr[[1,
5, 7, 2]][:, [0, 3, 1, 2]]
# array([[ 4, 7, 5, 6],
# [20, 23, 21, 22],
# [28, 31, 29, 30],
# [ 8, 11, 9, 10]])
|
上面的意思是,先从arr中选出[1, 5, 7, 2]这四行:
array([[
4, 5, 6, 7],
[20, 21, 22, 23],
[28, 29, 30, 31],
[ 8, 9, 10, 11]]) |
然后[:, [0, 3, 1, 2]]表示选中所有行,但是列的顺序要按0,3,1,2来排。于是得到:
array([[
4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]]) |
花式索引跟切片不同,总是将数据复制到新数组中。
数组转置和轴对换
转置是重塑的一种特殊形式,其返回源数据的视图(不会进行任何复制操作)。有两种方式,一是transpose方法,二是T属性。
arr
= np.arange(15).reshape((3, 5))
arr
# array([[ 0, 1, 2, 3, 4],
# [ 5, 6, 7, 8, 9],
# [10, 11, 12, 13, 14]])
arr.T
# array([[ 0, 5, 10],
# [ 1, 6, 11],
# [ 2, 7, 12],
# [ 3, 8, 13],
# [ 4, 9, 14]])
|
再进行矩阵计算时,常需要该操作,如利用np.dot计算内积:
arr
= np.random.randn(6,3)
np.dot(arr.T,arr)
# array([[ 1.8717599 , -1.66444711, -0.65044072],
# [-1.66444711, 6.02759713, 0.05453921],
# [-0.65044072, 0.05453921, 3.65394036]])
|
对于高维数组,transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置(比较费查克拉):
arr
= np.arange(16).reshape((2, 2, 4))
arr
# array([[[ 0, 1, 2, 3],
# [ 4, 5, 6, 7]],
# [[ 8, 9, 10, 11],
# [12, 13, 14, 15]]])
arr.transpose((1, 0, 2))
# array([[[ 0, 1, 2, 3],
# [ 8, 9, 10, 11]],
# [[ 4, 5, 6, 7],
# [12, 13, 14, 15]]])
|
其实就是把原本的轴按元组里的内容重排一下。简单的转置可以用.T。ndarray还有一个swapaxes方法,需要接受一对轴编号:
arr
# array([[[ 0, 1, 2, 3],
# [ 4, 5, 6, 7]],
# [[ 8, 9, 10, 11],
# [12, 13, 14, 15]]])
arr.swapaxes(1, 2)
# array([[[ 0, 4],
# [ 1, 5],
# [ 2, 6],
# [ 3, 7]],
# [[ 8, 12],
# [ 9, 13],
# [10, 14],
# [11, 15]]])
|
swapaxes也是返回数据的视图(不会进行任何复制操作)。
|