NumPy基础-数组和矢量计算
文章目录
高效处理大量数据,速度是内置纯python的10到100倍
- 使用连续的内存块存储数据,独立于其他python内置对象
- Numpy中C语言编写的算法库可以操作内存,而不必进行类型检查或其他前期的工作
- 占用的内存更少
- 可以在整个数组上执行复杂的计算,而不需要python的for循环、列表推导式等
import numpy as np
my_arr = np.arange(1000000)
my_list = list(range(1000000))
%time for _ in range(10): my_arr2 = my_arr * 2
%time for _ in range(10): my_list2 = [x * 2 for x in my_list]
Wall time: 27.9 ms
Wall time: 1.15 s
ndarray
- 一种多维数组对象
- 个人理解:可以直接对数据块进行数据运算,而不需要在for循环中写
- list、tuple等与数字相乘是将元素的数量复制
- 同构数据多维容器,内部元素数据类型必须一致
ll = list(range(1,11))
data = np.random.randn(2,3)
print(data)
# 列表元素复制3倍
print(ll * 3)
# ndarray内元素乘3
#乘法
print(data * 3)
# 加法
print(data + data)
#各维度大小
print(data.shape)
#数组类型
print(data.dtype)
#数组维度
print(data.ndim)
[[-0.45664989 0.24770378 0.29387936]
[-1.04888287 0.21683247 -1.30767515]]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[[-1.36994967 0.74311134 0.88163809]
[-3.1466486 0.65049741 -3.92302545]]
[[-0.91329978 0.49540756 0.58775872]
[-2.09776573 0.43366494 -2.6153503 ]]
(2, 3)
float64
2
要注意使用 import numpy as np,而不是使用from numpy import ,因为numpy中很多函数和方法,且有一些与python内置函数重名。
创建ndarray
- array方法创建,接受一切序列型的对象,包括其他数组
- zeros、ones分别可以创建指定长度或形状全0或全1数组,empty可以创建一个没有任何具体值的数组
- 通过arange方法创建方法创建
data1 = [1,2,3,4,5,6]
# 接受列表
arr = np.array(data1)
arr.shape
arr.ndim
arr
array([1, 2, 3, 4, 5, 6])
data2 = [[1,2,3],[4,5,6]]
# 接受嵌套序列
arr2 = np.array(data2)
arr2.shape
arr2.ndim
2
# 创建ndarray的方法
np.zeros(10)
np.ones((2,4))
np.empty((2,3,2))
np.arange(10)
np.arrange(0,10,0.1) # 起始 终点 步长
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
ndarray数据类型
- 可以在创建时指定dtype,将这一块内存指定为特定数据类型
- 要注意不同的数据类型所占的空间,尤其在处理大数据集时,就要控制存储类型,例如双精度浮点值就要占8个字节,即64位
arr1 = np.array([1,2.,3], dtype=np.float64)
arr1
array([1., 2., 3.])
- 可以使用astype将已有的数组转为其他数据类型
- 浮点数转为整数,小数部分会被截断
- 可将已有数组的类型传入到另外一个数组中
- 调用astype无论如何都会创建一个新的数组,原始数据会被拷贝
# 浮点数转为整数,小数部分被截断
arr = np.array([3.7,1.2,-2.6,10.1])
new_arr = arr.astype(np.int32)
# 传入arr数组的类型
arr1 = np.array([1,.2,.3])
arr1.astype(new_arr.dtype)
array([1, 0, 0])
数组与标量之间的运算
- 不用编写循环即可以对数据执行批量运算,这通常叫做矢量化(vectorization)
- 大小相同的数组之间的任何算数运算都会将运算应用到元素级上
- 不同大小的数组之间的运算叫做广播(broadcasting)(后续更新)
arr * arr
1/arr
array([ 0.27027027, 0.83333333, -0.38461538, 0.0990099 ])
基本的索引
- 在对数组一个切片进行赋值时,赋值会自动传播或称广播到整个选区
- 视图上的任何修改都会直接反映到源数据组上
- 若想得到ndarray切片的一份副本而非视图,就需要显式地进行复制操作 切片.copy()
- numpy设计的目的是处理大数据,如果将数据来回复制,必然产生内存和性能的问题
- 高维数组其实本质是一样的,需要注意的是索引和赋值时的维度
print(arr[3])
print(arr[1:3])
# 赋值&&传播
arr[1:3] = 5.5555
print(arr[1:3])
10.1
[66. 66.]
[5.5555 5.5555]
arr_slice = arr[1:4]
arr_slice[1:3] = 234
print(arr)
arr_slice[:] = 666
arr
[ 3.7 5.5555 234. 234. ]
array([ 3.7, 666. , 666. , 666. ])
# 高维数组操作
arr2d = np.array([[range(10)],[range(10)],[range(10)]])
arr2d
array([[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]],
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]],
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]])
arr2d[0] = 42
arr2d
array([[[42, 42, 42, 42, 42, 42, 42, 42, 42, 42]],
[[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]],
[[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]])
切片索引
- ndarray一维切片与python列表这样的一维对象差不多
arr = np.array([1,2,3,4,5,6])
arr[1:5]
array([2, 3, 4, 5])
- 高维切片的方式主要从轴向出发,沿轴切片,不指定轴,默认从第一个轴选取元素
arr2d = np.array([[1,2,3],[4,5,6],[7,8,9]])
arr2d[:2] #沿着竖轴切片
array([[1, 2, 3],
[4, 5, 6]])
arr3d = np.array([[[1,2],[3,4]],[[5,6],[7,8]],[[9,10],[11,12]]])
print(arr3d)
arr3d[:2,:1]
[[[ 1 2]
[ 3 4]]
[[ 5 6]
[ 7 8]]
[[ 9 10]
[11 12]]]
array([[[1, 2]],
[[5, 6]]])
arr3d[:2,:1,:1]
array([[[1]],
[[5]]])
- 上面这种切片方式只能得到相同维度的数组视图,冒号代表选取整个轴
- 通过将整数索引与切片混合的方式可以得到低维度的切片(整数代表选取的维度中的一维)
arr3d[0,:2]
array([[1, 2],
[3, 4]])
arr3d[2,:1,:1] #选取最后一个数组中,第一个列表的第一个元素
array([[9]])
- 冒号与不添加冒号对比
arr3d[-1:,:,:1] #整数-1代表最后一个,冒号代表整个轴
array([[[ 9],
[11]]])
arr3d[-1:,:1]
array([[[ 9, 10]]])
- 对切片表达式的赋值也会广播到整个选区
arr3d[:,:,-1:] = 0 #找到列表中最后一个元素,赋值为0
print(arr3d)
arr3d[:,-1:]
[[[ 1 0]
[ 3 0]]
[[ 5 0]
[ 7 0]]
[[ 9 0]
[11 0]]]
array([[[ 3, 0]],
[[ 7, 0]],
[[11, 0]]])
布尔型索引
- 数组的比较运算也是矢量化的
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
print(names)
print(data)
names == 'Bob' #矢量化产生数组
['Bob' 'Joe' 'Will' 'Bob' 'Will' 'Joe' 'Joe']
[[-0.63701615 -0.06376062 0.69585939 0.10945706]
[-0.97944937 -0.30974647 1.24323123 1.19624871]
[ 2.1017253 -0.29778746 -0.23660301 0.37227097]
[ 2.22638506 -0.30476552 1.01003157 -0.6791929 ]
[-0.1049328 -1.15896855 0.08494086 0.27167377]
[ 1.80679737 0.39574461 -1.43315823 1.31850162]
[-1.72153938 0.0493055 1.11043341 0.62209046]]
array([ True, False, False, True, False, False, False])
- 上面的布尔型数组可以用作数组索引
- 还可将布尔型数组跟切片、整数混合使用
- 除了使用等号,还可以使用(!=)(-)(|)(&)(> < )等多个布尔条件混合
- 使用~符号反转布尔型
- 与切片不同的是,使用布尔型索引选取数组中的数据,总是创建数据的副本,而不是修改数组的视图
print(data[names == 'Bob'])
print(data[names == 'Bob',:1])
print(data[~(names == 'Bob')])
[[-0.63701615 -0.06376062 0.69585939 0.10945706]
[ 2.22638506 -0.30476552 1.01003157 -0.6791929 ]]
[[-0.63701615]
[ 2.22638506]]
[[-0.97944937 -0.30974647 1.24323123 1.19624871]
[ 2.1017253 -0.29778746 -0.23660301 0.37227097]
[-0.1049328 -1.15896855 0.08494086 0.27167377]
[ 1.80679737 0.39574461 -1.43315823 1.31850162]
[-1.72153938 0.0493055 1.11043341 0.62209046]]
mask = (names == 'Bob') | (names == 'Will')
mask
array([ True, False, True, True, True, False, False])
data[mask]
array([[-0.63701615, -0.06376062, 0.69585939, 0.10945706],
[ 2.1017253 , -0.29778746, -0.23660301, 0.37227097],
[ 2.22638506, -0.30476552, 1.01003157, -0.6791929 ],
[-0.1049328 , -1.15896855, 0.08494086, 0.27167377]])
- 通过布尔型数组设置值非常常见!!
- 通过一维布尔数组设置整行或列的值也很简单
data[data>0.1] = 0 #将data中大于0.1的数值都设为0
print(data)
data[names == 'Bob'] = 7
print(data)
[[-0.63701615 -0.06376062 0. 0. ]
[-0.97944937 -0.30974647 0. 0. ]
[ 0. -0.29778746 -0.23660301 0. ]
[ 0. -0.30476552 0. -0.6791929 ]
[-0.1049328 -1.15896855 0.08494086 0. ]
[ 0. 0. -1.43315823 0. ]
[-1.72153938 0.0493055 0. 0. ]]
[[ 7. 7. 7. 7. ]
[-0.97944937 -0.30974647 0. 0. ]
[ 0. -0.29778746 -0.23660301 0. ]
[ 7. 7. 7. 7. ]
[-0.1049328 -1.15896855 0.08494086 0. ]
[ 0. 0. -1.43315823 0. ]
[-1.72153938 0.0493055 0. 0. ]]
花式索引(Fancy indexing)
- 利用整数数组进行索引
- 花式索引也是返回新数组,与切片不同
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.]]
arr[[4,3,2,1]] #传入一个指定顺序的整数序列即可返回指定行
array([[4., 4., 4., 4.],
[3., 3., 3., 3.],
[2., 2., 2., 2.],
[1., 1., 1., 1.]])
- 一次传入多个索引数组返回的是一个一维数组,其中的元素对应各个索引元组
- 若想返回行的子集,需要用冒号或np.ix_
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[[4,3,2,1],[0,1,2,3]]) #返回的是选取的那行的对应的元素
print(arr[[4,3,2,1]][:,[0,1,2]]) #返回选取行的前3个数
print(arr[np.ix_([4,3,2,1],[0,1,2])]) #返回选取行的前3个数
[16 13 10 7]
[[16 17 18]
[12 13 14]
[ 8 9 10]
[ 4 5 6]]
[[16 17 18]
[12 13 14]
[ 8 9 10]
[ 4 5 6]]
数据转置和轴对换
- 转置返回的是源数据的视图,不会进行复制操作
- 高维数组,转置需要得到一个由轴编号组成的元组才能对这些轴进行转置
- ndarray还有一个swapaxes方法,它需要接受一对轴编号,返回源数据的视图
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.T, arr) #计算矩阵内积
array([[125, 140, 155, 170, 185],
[140, 158, 176, 194, 212],
[155, 176, 197, 218, 239],
[170, 194, 218, 242, 266],
[185, 212, 239, 266, 293]])
- Transpose与swapaxes的转置内涵就是将shape索引置换,例如其中元素8的索引是(1,0,0),通过transpose(1,0,2)的转换,意思为一维与二维之间的索引互换,三维不变,那么元素8的新位置为(0,1,0)
arr = np.arange(16).reshape((2, 2, 4))
print(arr)
print(arr[1,0,0])
print(arr[0,1,0])
print(arr[0,0,0]) #置换后仍为(0,0,0)
print(arr[0,0,1]) #置换后仍为(0,0,1)
[[[ 0 1 2 3]
[ 4 5 6 7]]
[[ 8 9 10 11]
[12 13 14 15]]]
8
4
0
1
arr.transpose((1, 0, 2))
array([[[ 0, 1, 2, 3],
[ 8, 9, 10, 11]],
[[ 4, 5, 6, 7],
[12, 13, 14, 15]]])
arr.swapaxes(1, 2) #将索引(0,1,2)对换为(0,2,1)
array([[[ 0, 4],
[ 1, 5],
[ 2, 6],
[ 3, 7]],
[[ 8, 12],
[ 9, 13],
[10, 14],
[11, 15]]])