文章目录
0 写在前面
- ndarray,是一个具有矢量算术运算和复杂广播能力的快速,且节省空间的多维数组;
- 搞明白具体的性能差距,考察一个包含一百万整数的数组,和一个等价的Python列表。
- ndarray,是一个通用的同构数据多维容器,也就是说,所有的元素必须是相同类型的。每一个数组都有一个shape(表示各维度大小的元组)和一个dtype(一个用于说明数组数据类型的对象)
import numpy as np
my_arr = np.arange(1000000)
my_list = list(range(1000000))
%time for _ in range(10): my_arr2 = my_arr * 2
Wall time: 21.7 ms
%time for _ in range(10): my_list2 = [x * 2 for x in my_list]
Wall time: 855 ms
- 基于NumPy的算法要比纯Python快10到100倍(甚至更快),并且使用的内存更少。
1 ndarray:一种多维数组对象
1.1 numpy.random.randn(d0,d1, …,dn) 函数介绍
- rand() 函数,会根据指定的维度,生成[0,1) 之间的数据;
- dn 表示每个维度
- 返回值为指定维度的 array
代码:
import numpy as np
data = np.random.randn(2, 3)
print(data)
输出:
[[-0.98166625 -0.75308133 1.02950877]
[ 0.92072303 -0.98693389 -0.68642432]]
1.2 numpy.random.seed() 函数介绍
- 使得随机数据可预测
- 当设置相同的seed时,那么每次生成的随机数相同;如果不设置seed,则每次会生成不同的随机数。
1.3 创建 ndarray
1.3.1 np.array()
- 创建数组最简单的办法就是使用
array
函数。它接受一切序列型的对象(包括其他数组),然后返回一个NumPy数组。 - 以一个列表的转换为例:
In [19]: data1 = [6, 7.5, 8, 0, 1]
In [20]: arr1 = np.array(data1)
In [21]: arr1
Out[21]: array([ 6. , 7.5, 8. , 0. , 1. ])
- 嵌套序列(比如由一组等长列表组成的列表)将会被转换为一个多维数组:
In [22]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
In [23]: arr2 = np.array(data2)
In [24]: arr2
Out[24]:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
- Numpy数组有两个属性:
ndim
和shape
In [22]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
In [23]: arr2 = np.array(data2)
In [24]: arr2
Out[24]:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
In [25]: arr2.ndim
Out[25]: 2
In [26]: arr2.shape
Out[26]: (2, 4)
1.3.2 np.zeros()和np.ones()
- 需要传入的是,创建数组的指定长度或形状。
In [22]: data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
In [23]: arr2 = np.array(data2)
In [24]: arr2
Out[24]:
array([[1, 2, 3, 4],
[5, 6, 7, 8]])
1.3.3 np.empty()
- empty可以创建一个没有任何具体值的数组,因此只需要传入一个表示形状的元组即可。
In [31]: np.empty((2, 3, 2))
Out[31]:
array([[[ 0., 0.],
[ 0., 0.],
[ 0., 0.]],
[[ 0., 0.],
[ 0., 0.],
[ 0., 0.]]])
注意:认为np.empty会返回全0数组的想法是不安全的。很多情况下(如前所示),它返回的都是一些未初始化的垃圾值。
1.3.4 np.arange()
arange
是Python内置函数range的数组版:
In [32]: np.arange(15)
Out[32]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
1.3.5 函数总结
由于NumPy关注的是数值计算,因此,如果没有特别指定,数据类型基本都是float64(浮点数)。
1.4 ndarray的数据类型
前面说了,ndarray默认是 float64
但是可以用dtype
进行指定:
In [33]: arr1 = np.array([1, 2, 3], dtype=np.float64)
In [34]: arr2 = np.array([1, 2, 3], dtype=np.int32)
In [35]: arr1.dtype
Out[35]: dtype('float64')
In [36]: arr2.dtype
Out[36]: dtype('int32')
1.4.1 np.astype()
- 你可以通过ndarray的
astype
方法明确地将一个数组从一个dtype转换成另一个dtype:
In [37]: arr = np.array([1, 2, 3, 4, 5])
In [38]: arr.dtype
Out[38]: dtype('int64')
In [39]: float_arr = arr.astype(np.float64)
In [40]: float_arr.dtype
Out[40]: dtype('float64')
- 如果将浮点数转换成整数,则小数部分将会被截取删除:
In [41]: arr = np.array([3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
In [42]: arr
Out[42]: array([ 3.7, -1.2, -2.6, 0.5, 12.9, 10.1])
In [43]: arr.astype(np.int32)
Out[43]: array([ 3, -1, -2, 0, 12, 10], dtype=int32)
2 NumPy数组的运算
-
数组很重要,因为它使你不用编写循环,也可以对数据执行批量运算。
-
大小相等的数组,之间的任何算术运算,都会应用到元素级。
In [51]: arr = np.array([[1., 2., 3.], [4., 5., 6.]])
In [52]: arr
Out[52]:
array([[ 1., 2., 3.],
[ 4., 5., 6.]])
In [53]: arr * arr
Out[53]:
array([[ 1., 4., 9.],
[ 16., 25., 36.]])
In [54]: arr - arr
Out[54]:
array([[ 0., 0., 0.],
[ 0., 0., 0.]])
- 数组与标量的算术运算,会将标量值传播到各个元素:
In [55]: 1 / arr
Out[55]:
array([[ 1. , 0.5 , 0.3333],
[ 0.25 , 0.2 , 0.1667]])
In [56]: arr ** 0.5
Out[56]:
array([[ 1. , 1.4142, 1.7321],
[ 2. , 2.2361, 2.4495]])
- 大小相同的数组之间的比较会生成布尔值数组:
In [57]: arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
In [58]: arr2
Out[58]:
array([[ 0., 4., 1.],
[ 7., 2., 12.]])
In [59]: arr2 > arr
Out[59]:
array([[False, True, False],
[ True, False, True]], dtype=bool)
2.1 基本的索引和切片
- 简单用法如下:
可以发现,如果将一个标量赋给一个切片的话,那么这一段的切片内容都会发生改变;
而且数组切片是原始数组视图,在视图上的任何修改都会反映到原始数组上。
扫描二维码关注公众号,回复:
14328854 查看本文章
In [60]: arr = np.arange(10)
In [61]: arr
Out[61]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [62]: arr[5]
Out[62]: 5
In [63]: arr[5:8]
Out[63]: array([5, 6, 7])
In [64]: arr[5:8] = 12
In [65]: arr
Out[65]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
如果不想对原数组进行操作,那么几一定要明确的进行复制操作。如:
arr[5:8].copy()
- 在一个二维数组中,各索引位置上的元素不再是标量而是一维数组。
In [72]: arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
In [73]: arr2d[2]
Out[73]: array([7, 8, 9])
- 可以传入一个以逗号隔开的索引列表来选取单个元素:
In [74]: arr2d[0][2]
Out[74]: 3
In [75]: arr2d[0, 2]
Out[75]: 3
2.2 切片索引
- ndarry的切片语法,跟Python列表的一维数组差不多:
- 注意
arr[1:6]
是左闭右开的。
In [88]: arr
Out[88]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
In [89]: arr[1:6]
Out[89]: array([ 1, 2, 3, 4, 64])
- 对于之前的二维数组arr2d,其切片方式稍显不同:
- 可以看出,这里是沿着第0轴切片的,也就是说选取arr2d的前两行。
In [90]: arr2d
Out[90]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
In [91]: arr2d[:2]
Out[91]:
array([[1, 2, 3],
[4, 5, 6]])
- 传入多个切片:
In [92]: arr2d[:2, 1:]
Out[92]:
array([[2, 3],
[5, 6]])
- 将整数索引和切片混合,可以得到低维度的切片:
In [93]: arr2d[1, :2]
Out[93]: array([4, 5])
In [94]: arr2d[:2, 2]
Out[94]: array([3, 6])
- 只有
冒号 :
表示选取整个轴。
In [95]: arr2d[:, :1]
Out[95]:
array([[1],
[4],
[7]])
3 布尔型索引
- 例子:首先定义一个存储姓名的数组(7个名字),然后初始化一个7行的数组,每一行表示一个名字。
In [98]: names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
In [99]: data = np.random.randn(7, 4)
In [100]: names
Out[100]:
array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'],
dtype='<U4')
In [101]: data
Out[101]:
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.0072, -1.2962, 0.275 , 0.2289],
[ 1.3529, 0.8864, -2.0016, -0.3718],
[ 1.669 , -0.4386, -0.5397, 0.477 ],
[ 3.2489, -1.0212, -0.5771, 0.1241],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[-0.7135, -0.8312, -2.3702, -1.8608]])
- 现在的需求是,根据Bob对应在数组中的索引,选出对应的随机数据的对应行的数据。
- 可以看到,
names == 'Bob'
会生成一个bool类型的数组。
In [102]: names == 'Bob'
Out[102]: array([ True, False, False, True, False, False, False], dtype=bool)
- 写出来的代码就如下所示:
In [103]: data[names == 'Bob']
Out[103]:
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.669 , -0.4386, -0.5397, 0.477 ]])
- 选取了names == 'Bob’的行,并索引了列:
In [104]: data[names == 'Bob', 2:]
Out[104]:
array([[ 0.769 , 1.2464],
[-0.5397, 0.477 ]])
In [105]: data[names == 'Bob', 3]
Out[105]: array([ 1.2464, 0.477 ])
- 选取这三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔算术运算符即可:
In [110]: mask = (names == 'Bob') | (names == 'Will')
In [111]: mask
Out[111]: array([ True, False, True, True, True, False, False], dtype=bool)
In [112]: data[mask]
Out[112]:
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.3529, 0.8864, -2.0016, -0.3718],
[ 1.669 , -0.4386, -0.5397, 0.477 ],
[ 3.2489, -1.0212, -0.5771, 0.1241]])
- 通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0,我们只需:
In [113]: data[data < 0] = 0
In [114]: data
Out[114]:
array([[ 0.0929, 0.2817, 0.769 , 1.2464],
[ 1.0072, 0. , 0.275 , 0.2289],
[ 1.3529, 0.8864, 0. , 0. ],
[ 1.669 , 0. , 0. , 0.477 ],
[ 3.2489, 0. , 0. , 0.1241],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[ 0. , 0. , 0. , 0. ]])
- 通过一维布尔数组设置 整行或整列的值也很简单:
In [115]: data[names != 'Joe'] = 7
In [116]: data
Out[116]:
array([[ 7. , 7. , 7. , 7. ],
[ 1.0072, 0. , 0.275 , 0.2289],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 7. , 7. , 7. , 7. ],
[ 0.3026, 0.5238, 0.0009, 1.3438],
[ 0. , 0. , 0. , 0. ]])
4 花式索引
花式索引,就是利用传入整数数组进行索引。
In [117]: arr = np.empty((8, 4))
In [118]: for i in range(8):
.....: arr[i] = i
In [119]: arr
Out[119]:
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即可:
In [120]: arr[[4, 3, 0, 6]]
Out[120]:
array([[ 4., 4., 4., 4.],
[ 3., 3., 3., 3.],
[ 0., 0., 0., 0.],
[ 6., 6., 6., 6.]])
- 使用负数索引将会从末尾开始选取行:
In [121]: arr[[-3, -5, -7]]
Out[121]:
array([[ 5., 5., 5., 5.],
[ 3., 3., 3., 3.],
[ 1., 1., 1., 1.]])
-
如果传入多个索引数组,返回的还是一个一维数组。
-
最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)。无论数组是多少维的,花式索引总是一维的。
In [122]: arr = np.arange(32).reshape((8, 4))
In [123]: arr
Out[123]:
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],
[24, 25, 26, 27],
[28, 29, 30, 31]])
In [124]: arr[[1, 5, 7, 2], [0, 3, 1, 2]]
Out[124]: array([ 4, 23, 29, 10])
5 数组转置和轴对换
- 转置返回的是源数据的视图。
- 数组不仅有transpose方法,还有一个特殊的
T
属性。
In [126]: arr = np.arange(15).reshape((3, 5))
In [127]: arr
Out[127]:
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
In [128]: arr.T
Out[128]:
array([[ 0, 5, 10],
[ 1, 6, 11],
[ 2, 7, 12],
[ 3, 8, 13],
[ 4, 9, 14]])
- 在进行矩阵计算时,经常需要这个操作。比如说利用
np.dot
计算矩阵内积。
In [129]: arr = np.random.randn(6, 3)
In [130]: arr
Out[130]:
array([[-0.8608, 0.5601, -1.2659],
[ 0.1198, -1.0635, 0.3329],
[-2.3594, -0.1995, -1.542 ],
[-0.9707, -1.307 , 0.2863],
[ 0.378 , -0.7539, 0.3313],
[ 1.3497, 0.0699, 0.2467]])
In [131]: np.dot(arr.T, arr)
Out[131]:
array([[ 9.2291, 0.9394, 4.948 ],
[ 0.9394, 3.7662, -1.3622],
[ 4.948 , -1.3622, 4.3437]])
- 简单的转置可以使用.T,它其实就是进行轴对换而已。
- ndarray还有一个
swapaxes
方法,它需要接受一对轴编号。也是返回的源数据的视图。
In [135]: arr
Out[135]:
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7]],
[[ 8, 9, 10, 11],
[12, 13, 14, 15]]])
In [136]: arr.swapaxes(1, 2)
Out[136]:
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])