Skip to content

Python | Numpy

Posted on:February 19, 2019

NumPy是一个开源的Python科学计算基础库:

NumPy库的标准导入方法(使用别名):

import numpy as np

注意,本文自此以后将「ndarray数组」简称为「数组」。

Table of contents

1. 什么是数组 (ndarray)

1.1 概念

数组 vs 列表

数据类型相同

重要概念

1.2 属性

arr = np.array([[1,2,3],
               [4,5,6]])
# 数组的维度,int
arr.ndim
2
# 数组的形状(每个维度的大小,从第0轴开始)
# 一般是一个tuple
arr.shape
(2, 3)
# 数组的总大小,shape中元素相乘
arr.size
6
# 数组元素的类型
arr.dtype
dtype('int64')
# 每个数组元素的字节大小
arr.itemsize
8

1.3 数据类型

常用:

np.array([True, False, True]).dtype
dtype('bool')

2. 数组的创建

2.1 从无到有

# 全零数组
np.zeros((2,3))
array([[0., 0., 0.],
       [0., 0., 0.]])
# 全1数组
np.ones((3,3))
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])
# 全指定值数组
np.full((2,3), 10)
array([[10, 10, 10],
       [10, 10, 10]])
# 单位矩阵
np.eye(4)
array([[1., 0., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])
# 创建一个线性序列数组,一般常跟reshape配合生成多维数组
np.arange(10)
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# 接受起始点、结束点、节点数量三个参数,均匀分配
np.linspace(0,5,6)
array([0., 1., 2., 3., 4., 5.])

2.2 从已有数据构建

# 从list
np.array([1,2,3])
array([1, 2, 3])
# 从tuple
np.array((1,2,3))
array([1, 2, 3])
# 使用dtype参数指定数组元素的类型
np.array([1,2,3], dtype='float32')
array([1., 2., 3.], dtype=float32)
# dtype可以是字符串,也可以是np的对象
np.array([1,2,3], dtype=np.float32)
array([1., 2., 3.], dtype=float32)
# 嵌套
np.array([[1,2,3],[4,5,6]])
array([[1, 2, 3],
       [4, 5, 6]])
# 复杂一点的例子,配合了列表解析
np.array([range(i,i+3) for i in [2,4,6]])
array([[2, 3, 4],
       [4, 5, 6],
       [6, 7, 8]])
# np.ones_like(a)
# 根据数组(序列)a的形状形成一个全1数组
x = [[1,2,3],[4,5,6]]
np.ones_like(x)
array([[1, 1, 1],
       [1, 1, 1]])
# np.zeros_like(a)
# 根据数组(序列)a的形状形成一个全1数组
np.zeros_like(x)
array([[0, 0, 0],
       [0, 0, 0]])
# np.full_like(a, val)
# 根据数组(序列)a的形状形成一个全val数组
np.full_like(x,111)
array([[111, 111, 111],
       [111, 111, 111]])

2.3 随机数函数

1) 简单随机数函数

# 设定随机数种子——伪随机的概念,数序
np.random.seed(0)
# np.random.random((d0,d1,...,dn))
# 返回一个或一组服从“0~1均匀分布”(包括0但不包括1)的随机样本值
np.random.random((2,3))
array([[0.5488135 , 0.71518937, 0.60276338],
       [0.54488318, 0.4236548 , 0.64589411]])
# np.random.rand(d0,d1,...,dn)
# 作用与np.random.random相同
# If you want an interface that takes a shape-tuple as the first argument
# refer to np.random.random
np.random.rand(2,3)
array([[0.56804456, 0.92559664, 0.07103606],
       [0.0871293 , 0.0202184 , 0.83261985]])
# np.random.randn(d0,d1,...dn)
# 根据形状创建随机数数组,浮点数,标准正态分布
np.random.randn(2,3)
array([[ 0.44386323,  0.33367433,  1.49407907],
       [-0.20515826,  0.3130677 , -0.85409574]])
# np.random.randint(low[,high,shape])
# 根据shape创建整数或整数数组,范围是[low,high)
# “If high is None (the default), then results are from [0,low)”
np.random.randint(0,100,(2,3))
array([[ 9, 57, 32],
       [31, 74, 23]])

2) 高级随机数函数

# np.random.uniform(low, high, size)
# 产生具有均匀分布的数据
# low起始值,high结束值,size形状,[low,high)
np.random.uniform(0,100,(3,4))
array([[69.76311959,  6.02254716, 66.67667154, 67.06378696],
       [21.03825611, 12.89262977, 31.54283509, 36.37107709],
       [57.01967704, 43.86015135, 98.83738381, 10.20448107]])
# np.random.normal(loc, scale, size)
# 产生具有正态分布的数据,loc均值,scale标准差,size形状
np.random.normal(5,2,(2,3))
array([[3.98069564, 4.1238514 , 2.49440928],
       [6.55498071, 1.7722043 , 4.57451944]])
# np.random.poisson(lam, size)
# 产生具有泊松分布的数据,lam随机事件发生率,size形状
np.random.poisson(2,(2,3))
array([[1, 1, 1],
       [1, 1, 5]])

3) 随机重排

# np.random.shuffle(a)
# 只接受一个参数
# 根据数组a的第0轴进行随机排列,改变数组a
a = np.arange(10)
np.random.shuffle(a)
a
array([0, 5, 7, 8, 1, 4, 9, 6, 2, 3])
# np.random.permutation(a)
# 同上,但不改变数组a
c = np.arange(10)
np.random.permutation(c)
array([1, 4, 7, 5, 2, 3, 8, 6, 9, 0])
c
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
# np.random.choice(a, [,size, replace, p])
# 从1维数组a中以概率p抽取元素,形成size形状的新数组
# replace表示是否可以重用元素,默认为Ture
d = np.random.randint(100,201,(8,))
d
array([140, 172, 119, 195, 172, 126, 166, 152])
np.random.choice(d, (2,3))
array([[195, 126, 195],
       [172, 166, 126]])
np.random.choice(d, (2,3), replace=False)
array([[119, 195, 152],
       [172, 140, 172]])
np.random.choice(d, (2,3), p=d/np.sum(d))
array([[126, 119, 195],
       [166, 152, 140]])

3. 数组的变形

3.1 reshape

.reshape(),常用于将一维数组扩展为多维数组:

np.arange(10).reshape(2,5)
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

3.2 resize

.resize(),效果和reshape一样,但修改原数组——reshape是返回一个新的数组而原数组不变。

a = np.arange(10)
a.resize(2,5)
a
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
a = np.arange(10)
a.reshape(2,5)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

3.3 newaxis

将一个一维数组,转变成为二维的行或列的矩阵(完全可以用reshape来做):

x = np.array([1,2,3])
x
array([1, 2, 3])
# 行矩阵 = x.reshape(1,3)
x[np.newaxis, :]
array([[1, 2, 3]])
# 列矩阵 = x.reshape(3,1)
x[:, np.newaxis]
array([[1],
       [2],
       [3]])

3.4 flatten

.flatten(),对数组进行降维,返回折叠后的一维数组,原数组不变。

a = np.array([[1,2,3],
             [4,5,6],
             [7,8,9]])
a.flatten()
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
a
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

3.5 astype

.astype(),对数组元素进行类型变换,注意astype方法一定会创建新的数组,即使两个类型一样。

# new_a = a.astype(new_type)
a = np.ones((2,2), dtype="int32")
a
array([[1, 1],
       [1, 1]], dtype=int32)
a.astype("float32")
array([1.9, 1.9, 1.9, 2.8], dtype=float32)

4. 索引与切片

4.1 普通索引

支持正向索引与反向索引:

a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[1]
1
a[-2]
8

多维数组可以用一个中括号辅以「逗号」来获取深层元素,如a[0,0]和普通list的a[0][0]等价。

b = np.arange(12).reshape(3,4)
b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
# 第2行第2列元素
b[1,1]
5

可以使用索引直接修改数组。注意,试图将一个浮点数插入到一个整型数组中,会发生无任何警告的截断操作(不是四舍五入)。

b[-1,-1]
11
b[-1,-1] = 199.9999999
b
array([[  0,   1,   2,   3],
       [  4,   5,   6,   7],
       [  8,   9,  10, 199]])

4.2 花式索引

fancy indexing,传递一个索引数组来一次性获得多个数组元素。

x = np.array([1,2,3,4,5,6,7])
ind = [0,1,2,3]
x[ind]
array([1, 2, 3, 4])

多维数组的花式索引:

X = np.arange(24).reshape((4,6))
X
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]])
# 第一个值是X[1,2],第二个值是X[3,4]
row = np.array([1,3])
col = np.array([2,4])
X[row, col]
array([ 8, 22])
# 交换任意两行
A = np.arange(25).reshape(5,5)
A
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]])
# A[[0,1]] 等价于 A[[0,1],] 等价于 A[[0,1],:],表示前两行
A[[0,1]] = A[[1,0]]
A
array([[ 5,  6,  7,  8,  9],
       [ 0,  1,  2,  3,  4],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19],
       [20, 21, 22, 23, 24]])

4.3 切片

索引获取单个值,切片获取子数组。语法为x[start:stop:step],默认值分别为start=0, stop=size of dimension, step=1。step为负的时候表示逆向截取。

a = np.arange(10)
a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[1:7]
array([1, 2, 3, 4, 5, 6])
a[::-1]
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

多维数组的切片不同维度用逗号分隔进行操作。对于二维数组xx[:2, :3]表示切到前两行+前三列。

b = np.arange(12).reshape(3,4)
b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
b[:2,:2]
array([[0, 1],
       [4, 5]])

获取数组的单行或单列:将切片和索引配合使用。

# 单行
b[1,:]
array([4, 5, 6, 7])
# 单列
b[:,1]
array([1, 5, 9])

关于副本(copy)和视图(view):切片切出来的是view而非copy,故对切片的修改会影响到原数组。

b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
temp = b[0,:]
temp
array([0, 1, 2, 3])
temp[1] = 100
temp
array([  0, 100,   2,   3])
b
array([[  0, 100,   2,   3],
       [  4,   5,   6,   7],
       [  8,   9,  10,  11]])

目的:这样做可以节省缓存空间。可以使用切片.copy()获得副本;对副本操作,不会影响原数组。

5. 拼接与分裂

5.1 拼接

将多个数组拼接成一个。

1) concatenate

np.concatenate(),默认按照第0轴拼接,可以一次性拼接多个,但要把拼接的数组先以列表或元组的形式圈起来,如np.concatenate([x,y,z])

a1 = np.arange(6).reshape(2,3)
a1
array([[0, 1, 2],
       [3, 4, 5]])
addarr = np.array([6,7,8])
addarr
array([6, 7, 8])
add1 = addarr[np.newaxis, :]
np.concatenate([a1, add1])
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

对于多维数组的拼接,np.concatenate([x,y], axix=指定轴)

add2 = addarr[:-1, np.newaxis]
np.concatenate([a1, add2], axis=1)
array([[0, 1, 2, 6],
       [3, 4, 5, 7]])

2) vstack & hstack

a1
array([[0, 1, 2],
       [3, 4, 5]])

np.vstack: 垂直栈。垂直方向对接,同样要把拼接的数组先以列表或元组的形式圈起来,但拼上去的一维数组不需要先转成2维再拼接,可以直接拼接。

np.vstack([a1,a1])
array([[0, 1, 2],
       [3, 4, 5],
       [0, 1, 2],
       [3, 4, 5]])
np.vstack([a1,a1[0]])
array([[0, 1, 2],
       [3, 4, 5],
       [0, 1, 2]])
# 并不需要
np.vstack([a1,a1[0][np.newaxis,:]])
array([[0, 1, 2],
       [3, 4, 5],
       [0, 1, 2]])

np.hstack:水平栈。水平方向拼接,同样要把拼接的数组先以列表或元组的形式圈起来。

np.hstack([a1,a1])
array([[0, 1, 2, 0, 1, 2],
       [3, 4, 5, 3, 4, 5]])
np.hstack([a1,a1[:,1][:,np.newaxis]])
array([[0, 1, 2, 1],
       [3, 4, 5, 4]])

5.2 分裂(略)

np.split(x, [...]):第一个参数是要进行分裂的数组,第二个参数是一个索引列表,记录分裂点。

x = [1,2,3,99,99,3,2,1]
x1,x2,x3 = np.split(x, [3,5])
print(x1,x2,x3)
[1 2 3] [99 99] [3 2 1]

np.vsplit(): 垂直栈,分开。

grid = np.arange(16).reshape(4,4)
grid
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
upper, lower = np.vsplit(grid, [2])
print(upper)
print(lower)
[[0 1 2 3]
 [4 5 6 7]]
[[ 8  9 10 11]
 [12 13 14 15]]

np.hsplit():水平栈,分开。

left, right = np.hsplit(grid, [2])
print(left)
print(right)
[[ 0  1]
 [ 4  5]
 [ 8  9]
 [12 13]]
[[ 2  3]
 [ 6  7]
 [10 11]
 [14 15]]

6. 广播

广播的实质:用于不同大小数组的二元通用函数的一组规则——Broadcasting allows computation to be performed on arrays of different sizes.

参考资料:广播的直观理解

a = 5
b = np.array([1,2,3])
a+b
array([6, 7, 8])

相当于把5扩展成array([5,5,5]),然后再相加。但是Numpy的强大之处在于,并没有显性地执行这种duplication,节省了很多空间。

c = np.array([[1,1,1],
             [2,2,2],
             [3,3,3]])
b+c
array([[2, 3, 4],
       [3, 4, 5],
       [4, 5, 6]])

相当于把[1,2,3]扩展成[[1,2,3],[1,2,3],[1,2,3]],然后再相加。

广播规则:

失败案例:

M = np.ones((3, 2))
a = np.arange(3)

形状:

根据Rule 1:

根据Rule 2:

根据Rule 3:

X = np.arange(24).reshape((4,6))
row = np.array([1,3])
col = np.array([2,4])
X[row, col]
array([ 8, 22])
# 广播,十分有用,交叉点元素
# row[:, np.newaxis] - 列向量1,3
# col - 行向量2,4
X[row[:, np.newaxis], col]
array([[ 8, 10],
       [20, 22]])

8. 函数

8.1 通用函数

1) 算术运算

原生的算术运算符,实质上是NumPy通用函数的封装器,例如+就是np.add的封装器。

x = np.zeros(5)
x
array([0., 0., 0., 0., 0.])
x+2
array([2., 2., 2., 2., 2.])
np.add(x,2)
array([2., 2., 2., 2., 2.])
运算符 对应的通用函数 描述
+ np.add 加法
- np.subtract 减法
* np.multiply 乘法
/ np.divide 除法
// np.floor_divide 向下整除
** np.power 指数
% np.mod 取余

2) 其他

8.2 聚合函数

最常用的三种:

轴的再理解:参考资料

a = np.array([[[1,2,3],
              [1,2,3],
              [1,2,3]],
             [[4,5,6],
             [4,5,6],
             [4,5,6]]])
a
array([[[1, 2, 3],
        [1, 2, 3],
        [1, 2, 3]],

       [[4, 5, 6],
        [4, 5, 6],
        [4, 5, 6]]])
np.sum(a)
63
a.shape
(2, 3, 3)
# keepdims,保留维度信息
np.sum(a, axis=0, keepdims=True)
array([[[5, 7, 9],
        [5, 7, 9],
        [5, 7, 9]]])
np.sum(a, axis=1, keepdims=True)
array([[[ 3,  6,  9]],

       [[12, 15, 18]]])
np.sum(a, axis=2, keepdims=True)
array([[[ 6],
        [ 6],
        [ 6]],

       [[15],
        [15],
        [15]]])

理解:axis指定的是数组将会被折叠的维度。

大多数聚合函数都应版本的NaN-safe函数,即计算时忽略所有的缺失值,只需在名字前面加nan。例如,np.sum的缺失值安全版本是np.nansum

其他常用聚合函数:

9. 比较与逻辑

场景:希望基于某些准则来抽取、修改、计数或对一个数组中的值进行其他操作。

9.1 比较运算与布尔数组

比较运算符与对应的通用函数(返回布尔数据类型的数组):

# x==3就返回一个布尔数组
x = np.array([1,2,3,4,5])
x == 3
array([False, False,  True, False, False])

对布尔数组的常用操作:

np.count_nonzero(x==3)
1
np.any(x==3)
True
np.all(x==3)
False

9.2 掩码

使用布尔数组作为掩码(mask),通过该掩码选择数据的子数据集:

x
array([1, 2, 3, 4, 5])
# 抽取x中大于3的元素
x[x>3]
array([4, 5])
y = np.arange(12).reshape(3,4)
y
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
# 抽取y中大于等于7的元素,展开
y[y>=7]
array([ 7,  8,  9, 10, 11])

9.3 逐位运算符

逐位布尔运算符:

andor对整个对象执行单个布尔操作,而&|对一个对象的内容执行多个布尔运算,下例:

a = np.array([True, False, True])
b = np.array([True, True, False])
# 对a和b中每一对对应的元素进行“逻辑与”的运算
a & b
array([ True, False, False])
# 对a和b中每一对对应的元素进行“逻辑或”的运算
a | b
array([ True,  True,  True])

10. 其他重要方法

10.1 np.meshgrid

生成网格点坐标矩阵(类似双列表解析):

x = [1,2,3]
y = [1,2]
np.meshgrid(x,y)
# [(i,j) for i in x for j in y]
[array([[1, 2, 3],
        [1, 2, 3]]), array([[1, 1, 1],
        [2, 2, 2]])]

用途:绘制等高线图。

10.2 np.where

np.where(condition, x, y):满足条件,返回x,否则返回y。

aa = np.array([[1,2],
              [3,4],
              [5,6]])
np.where(aa>=3, 1, -1)
array([[-1, -1],
       [ 1,  1],
       [ 1,  1]])

np.where(condition):找到数组中特定数值的索引:

bb = np.array([[1,2,3],
              [3,4,5]])
np.where(bb>=3)
(array([0, 1, 1, 1]), array([2, 0, 1, 2]))

10.3 np.sort

一维数组排序(重新创建而非就地修改):

np.random.seed(0)
a = np.random.randint(0,20,(10))
a
array([12, 15,  0,  3,  3,  7,  9, 19, 18,  4])
# 默认升序
np.sort(a)
array([ 0,  3,  3,  4,  7,  9, 12, 15, 18, 19])
# 数组排好序的索引值
np.argsort(a)
array([2, 3, 4, 9, 5, 6, 0, 1, 8, 7])

多维数组排序,用axis=指定排序的轴,注意这样排序会导致任何行或列之间的关系丢失!

np.random.seed(1)
X = np.random.randint(0,100,(4,5))
X
array([[37, 12, 72,  9, 75],
       [ 5, 79, 64, 16,  1],
       [76, 71,  6, 25, 50],
       [20, 18, 84, 11, 28]])
np.sort(X, axis=0)
array([[ 5, 12,  6,  9,  1],
       [20, 18, 64, 11, 28],
       [37, 71, 72, 16, 50],
       [76, 79, 84, 25, 75]])
np.sort(X, axis=1)
array([[ 9, 12, 37, 72, 75],
       [ 1,  5, 16, 64, 79],
       [ 6, 25, 50, 71, 76],
       [11, 18, 20, 28, 84]])

10.4 np.insert

给矩阵插入行或列。

a = np.array([[1,0,2,3],
             [1,0,2,3],
             [1,0,2,3]])
a
array([[1, 0, 2, 3],
       [1, 0, 2, 3],
       [1, 0, 2, 3]])
np.insert(a, 1, values = np.ones((2,a.shape[1])), axis = 0)
array([[1, 0, 2, 3],
       [1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 0, 2, 3],
       [1, 0, 2, 3]])