NumPy(Numerical Python的简称)是Python数值计算最重要的基础包。大多数提供科学计算的包都是用NumPy的数组作为构建基础。本篇总结梳理了数据分析中使用频率相对较高的一些NumPy基本命令。
import numpy as np
ndarray
: 一种多维数组对象
ndarray
是一个通用的同构数据多维容器,也就是说,其中的所有元素必须是相同类型的。每个数组都有一个shape
(一个表示各维度大小的元组)、一个dtype
(一个用于说明数组数据类型的对象)和一个strides
(一个用于说明为了前进到当前维度下一个元素需要“跨过”的字节数的跨度元组)。
常用命令:
ndarray
:
np.array()
,将输入数据(列表、元组、数组或其它序列类型)转换为ndarray
。要么推断出dtype
,要么指定特别dtype
。默认直接复制输入数据。np.zeros()
: 创建指定长度或形状的全0数组,只需传入一个表示形状的元组。np.zeros_like()
: 返回与指定数组同样形状和数据类型的全0数组。np.ones()
: 创建指定长度或形状的全1数组,只需传入一个表示形状的元组。np.ones_like()
: 返回与指定数组同样形状和数据类型的全1数组。np.empty()
| np.empty_like()
: 创建没有任何具体值的数组。np.arange()
:类似于内置的range
,但返回的是一个ndarray
而不是列表。np.full(shape, fill_value)
|np.full_like()
:用fill_value
中的所有值,根据指定的形状和dtype
创建数组。shape
: a.shape
a.dtype
a.astype(dtype)
np.issubdtype(a.dtype, np.integer)
,np.issubdtype(a.dtype, np.floating)
,np.issubdtype(a.dtype, np.number)
a.reshape(shape, order = 'C')
,它返回的是源数据的视图(不会进行任何复制操作)。注意,作为参数的形状的其中一维可以是-1
,它表示该维度的大小由数据本身推断而来。下图说明了order
参数所起的作用:a.ravel()
:扁平化或散开,不产生源数据的副本a.flatten()
:扁平化或散开,但产生源数据的副本a.T
,用于简单的转置即轴转换,在进行矩阵内积运算时会用到此命令。a.transpose(*axes)
,用于高维数组,*axes
为由轴编号组成的元组。a.swapaxes(axis1, axis2)
,对axis1
和axis1
进行轴转换。同样返回的是源数据的视图。np.concatenate((a1,a2,....), axis=0)
:按指定轴将一个由数组组成的序列(如元组、列表等)连接到一起。np.hstack(tup)
:将2个矩阵按列合并。np.vstack(tup)
:将2个矩阵按行合并。np.split(a, indices_or_sections, axis=0)
:将一个数组沿指定轴拆分为多个数组。a.repeat(repeats, axis=None)
:将各个元素重复一定的数量(repeats
)。默认情况下,如果传入的是一组整数,则各个元素可以重复不同的次数。np.tile(A, reps)
:沿指定轴堆叠数组的副本。reps
为表示堆叠布局的元组或标量,如果是标量,默认为水平堆叠。np.random.normal()
:标准正态(高斯)分布样本值np.random.seed()
:更改随机数生成种子np.random.rand
:产生均匀分布的样本值np.random.randint(low,high,size)
:从给定的上(exclusive)下(inclusive)限范围内随机选取整数np.random.randn
:产生正态分布(平均值为0,标准差为1)的样本值np.random.shuffle
:对一个序列就地随机排列np.random.permutation
:返回一个序列的随机排列或返回一个随机排列的范围Numpy
数组的运算
比如,下方的例子中,希望通过减去列平均值的方式对数组的每一列进行距平化处理。因为arr.mean(0)
的形状为(3,)
,而arr
的后缘维度为3
,其与arr
的后缘维度是相同的,所以它们是广播兼容的。广播在行的维度上进行。
# 对数组每一列进行距平均化
>>> arr = np.random.randn(4, 3)
>>> arr
array([[-0.18889954, -2.00928225, -2.3916824 ],
[-2.46055652, -1.02446526, -1.03097103],
[ 0.48938275, 0.37362381, 0.63674138],
[-1.41777675, 0.58666021, -0.86618113]])
>>> arr.mean(0).shape
(3,)
>>> arr - arr.mean(0)
array([[ 0.70556298, -1.49091638, -1.4786591 ],
[-1.56609401, -0.50609939, -0.11794773],
[ 1.38384527, 0.89198968, 1.54976468],
[-0.52331424, 1.10502608, 0.04684216]])
而如果希望对各行减去行平均值,因为arr.mean(1)
的形状为(4,)
,而arr
的后缘维度为3
,其与arr
的后缘维度不同。根据广播原则,要在1轴上广播做减法,较小的那个数组的形状必须是(4,1)
。
# 对数组每一行进行距平均化
>>> arr
array([[-0.18889954, -2.00928225, -2.3916824 ],
[-2.46055652, -1.02446526, -1.03097103],
[ 0.48938275, 0.37362381, 0.63674138],
[-1.41777675, 0.58666021, -0.86618113]])
>>> arr.mean(1).shape
(4,)
>>> arr - arr.mean(1)
Traceback (most recent call last):
File "" , line 1, in <module>
ValueError: operands could not be broadcast together with shapes (4,3) (4,)
>>> arr - arr.mean(1).reshape((4,1))
array([[ 1.34105519, -0.47932753, -0.86172767],
[-0.95522558, 0.48086568, 0.47435991],
[-0.01053323, -0.12629217, 0.1368254 ],
[-0.85201086, 1.1524261 , -0.30041524]])
对于三维的情况,下图说明了要在三维数组各维度上广播的形状需求:
在为了广播而添加一个长度为1的新轴时,可以使用np.newaxis
属性以及“全”切片来插入新轴:
>>> arr = np.zeros((4,4))
>>> arr
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
>>> arr_3d = arr[:,np.newaxis,:]
>>> arr_3d.shape
(4, 1, 4)
>>> arr_1d = np.random.normal(size=3)
>>> arr_1d
array([-0.53656014, -0.821661 , -0.07331854])
>>> arr_1d[:,np.newaxis]
array([[-0.53656014],
[-0.821661 ],
[-0.07331854]])
>>> arr_1d[np.newaxis,:]
array([[-0.53656014, -0.821661 , -0.07331854]])
说明:上述添加新轴的方式同样适用于通过广播进行赋值,只要保证形状兼容即可。
几种索引方式:基本索引、切片索引、布尔式索引、花式索引
基本索引:下图说明了二维数组基本的索引方式:
arr[start:end] = some value
:给数组中某个切片赋值,当将一个标量值赋值给一个切片时(如arr[5:8]=12
),该值会自动传播(即,“广播”)到整个选区。arr[:] = some value
:给数组中的所有值赋值。切片索引:切片默认是沿着第0轴(即第一个轴)切片的,如表达式arr[:2]
可以被认为是“选取arr
的前两行”。
>>> arr2d =np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> arr2d
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
>>> arr2d[:2]
array([[1, 2, 3],
[4, 5, 6]])
下图为二维数组切片的简单示例:
布尔型索引:组合应用多个布尔条件,使用&
(和)、|
(或)之类的布尔算术运算符,且每个条件需用括号括起来,比如 (cond1) | (cond2)
。注意:通过布尔型索引选取数组中的数据,将总是创建数据的副本。
花式索引:可以实现以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray
即可。而使用负数索引将会从末尾开始选取行。同样需注意,花式索引跟切片不一样,它总是将数据复制到新数组中。
一次传入多个索引数组会有一点特别。它返回的是一个一维数组,其中的元素对应各个索引元组,如下方的例子中,最终选出的元素是(1,0)、(5,3)、(7,1),(2,2):
>>> 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],
[24, 25, 26, 27],
[28, 29, 30, 31]])
>>> arr[[1,5,7,2],[0,3,1,2]]
array([ 4, 23, 29, 10])
需要说明:除布尔型索引、花式索引外,一般的情况下,数组切片是原始数组的视图。这意味着数据不会被复制,视图上的任何修改都会直接反映到源数组上。
注意下方的例子,选取arr
的一个切片arr_slice
,当修改arr_slice
时,原数组也发生了变化!
>>> arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> arr_slice = arr[5:8]
>>> arr_slice
array([5, 6, 7])
>>> arr_slice[1] = 12345
>>> arr
array([ 0, 1, 2, 3, 4, 5, 12345, 7, 8,
9])
注意:如果你想要得到的是ndarray切片的一份副本而非视图,就需要明确地进行复制操作,例如
arr[5:8].copy()
。
再看一下下方的例子:
>>> arr = np.arange(10)
>>> arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> arr_slice = arr[5:8]
>>> arr_slice
array([5, 6, 7])
>>> arr = np.arange(10)
>>> arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> arr_slice = arr[5:8].copy()
>>> arr_slice[1] = 12345
>>> arr_slice
array([ 5, 12345, 7])
>>> arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
通用函数(即
ufunc
)是一种对ndarray
中的数据执行元素级运算的函数。
unary
)ufunc
:接受1个数组binary
)ufunc
:接受2个数组注意:
Ufuncs
可以接受一个out可选参数,这样就能在数组原地进行操作。
比如下方示例:
>>> arr = np.random.randn(7) * 5
>>> arr
array([-5.93919409, -2.97839885, 1.18773417, 1.61830444, -6.56820877,
3.94290995, -3.82345097])
>>> np.sqrt(arr)
<stdin>:1: RuntimeWarning: invalid value encountered in sqrt
array([ nan, nan, 1.08983217, 1.27212595, nan,
1.98567619, nan])
>>> np.sqrt(arr,arr)
array([ nan, nan, 1.08983217, 1.27212595, nan,
1.98567619, nan])
>>> arr
array([ nan, nan, 1.08983217, 1.27212595, nan,
1.98567619, nan])
>>>
此外,还有一些高级的ufunc
应用:
reduce(x)
:通过连续执行原始运算的方式对值进行聚合,比如np.add.reduce(arr)
等同于np.sum(arr)
。accumulate(x)
:聚合值,保留所有局部聚合结果,比如np.add.accumulate(arr)
等同于np.cumsum(arr)
。reduceat(x,bins)
:“局部”约简(也就是groupby
)。约简数据的各个切片以产生聚合型数组,比如np.add.reduceat(arr,[0,5,8])
是在arr[0:5]
、arr[5:8]
以及arr[8:]
上执行的求和约简。outer(x,y)
:对x
和y
中的每对元素应用原始运算。结果数组的形状为x.shape
+ y.shape
。
NumPy
数组使你可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环),即矢量化。一般来说,矢量化数组运算要比等价的纯Python方式快上一两个数量级(甚至更多)。
常用命令:
np.where(condition, x,y)
:根据condition
选择x
中的值或y
中的值,是三元表达式x if condition else y
的矢量化表达;x
、y
可以是数组,也可以是标量值。a.sum()
:对数组中全部或某轴向的元素求和a.mean()
:算术平均数a.std()
|a.var()
:标准差和方差a.min()
|a.max()
:最小值和最大值a.argmin()
|a.argmax()
:最小和最大元素的索引a.cumsum()
:所有元素的累计和a.cumprod()
:所有元素的累计积
argmax
一个巧妙的用法:获取布尔型数组第一个最大值的索引(True就是最大值)。
比如,希望获取arr
第一个大于5的值的位置索引:
>>> arr = np.array([1,3,2,5,4,10,4])
>>> arr
array([ 1, 3, 2, 5, 4, 10, 4])
>>> (arr>4).argmax()
3
.sum()
:经常被用来对布尔型数组中的True
值计数.any()
:用于测试数组中是否存在一个或多个True
.all()
:用于检查数组中所有值是否都是True
a.sort(axis)
:在轴向上进行排序,顶级方法np.sort
返回的是数组的已排序副本,而就地排序则会修改数组本身。arr[:,::-1]
,返回一个列反序的列表。arr.argsort()
:返回用于数组排序的索引。arr.lexsort()
:类似arr.argsort()
,只不过可以一次性对多个键数组执行间接排序。arr.searchsorted(data)
:在有序数组arr
中查找data
中的元素插入后的位置索引。以下为使用argsort()
命令实现部分排序数组的示例:
# 根据数组的第一行排序
>>> arr = np.random.randn(3,5)
>>> values = np.array([5,0,1,3,2])
>>> arr[0] =values
>>> arr
array([[ 5.00000000e+00, 0.00000000e+00, 1.00000000e+00,
3.00000000e+00, 2.00000000e+00],
[ 1.23514614e+00, 1.35506346e-01, -7.05498872e-04,
2.53602483e-01, -1.83245736e-01],
[-7.06630296e-01, 4.26760743e-01, -2.77577070e-01,
-8.28284581e-01, -2.76283358e+00]])
>>> arr[:,arr[0].argsort()]
array([[ 0.00000000e+00, 1.00000000e+00, 2.00000000e+00,
3.00000000e+00, 5.00000000e+00],
[ 1.35506346e-01, -7.05498872e-04, -1.83245736e-01,
2.53602483e-01, 1.23514614e+00],
[ 4.26760743e-01, -2.77577070e-01, -2.76283358e+00,
-8.28284581e-01, -7.06630296e-01]])
以下为使用searchsorted
命令实现数据数组面元切分的示例:
# 根据“面元边界”数组拆分数据数组
>>> data = np.floor(np.random.uniform(0,10000,size=50))
>>> bins = np.array([0,100,1000,5000,10000])
>>> data
array([7.194e+03, 1.291e+03, 4.934e+03, 8.405e+03, 8.240e+03, 1.784e+03,
4.710e+03, 5.545e+03, 3.315e+03, 5.217e+03, 1.110e+02, 2.937e+03,
7.204e+03, 8.029e+03, 2.849e+03, 3.814e+03, 5.179e+03, 7.877e+03,
3.910e+02, 4.625e+03, 9.455e+03, 3.580e+02, 3.770e+03, 1.123e+03,
4.276e+03, 1.846e+03, 4.529e+03, 2.485e+03, 7.122e+03, 7.936e+03,
7.573e+03, 2.222e+03, 7.643e+03, 7.839e+03, 4.581e+03, 7.493e+03,
9.578e+03, 1.930e+02, 2.845e+03, 7.794e+03, 4.001e+03, 8.519e+03,
4.281e+03, 6.000e+00, 8.091e+03, 8.760e+02, 1.220e+03, 2.005e+03,
7.626e+03, 8.274e+03])
>>> labels = bins.searchsorted(data)
>>> labels
array([4, 3, 3, 4, 4, 3, 3, 4, 3, 4, 2, 3, 4, 4, 3, 3, 4, 4, 2, 3, 4, 2,
3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 4, 4, 3, 4, 4, 2, 3, 4, 3, 4, 3, 1,
4, 2, 3, 3, 4, 4], dtype=int64)
Tips: 因为
sort
方法不可以被设置的降序,这时可以使用类似value[::-1]
的方法产生一个反序的数组。
np.unique()
:找出数组中的唯一值并返回已排序的结果。np.intersect1d(x,y)
:计算x
和y
中的公共元素,并返回有序结果。np.union1d(x,y)
:计算x
和y
的并集,并返回有序结果。in1d(x,y)
:得到一个表示“x
的元素是否包含于y
”的布尔型数组。setdiff1d(x,y)
:集合的差,即元素在x
中且不在y
中。setxor1d(x,y)
:集合的对称差,即存在于一个数组中但不同时存在于两个数据中的元素。