数组 (ndarray)介绍

这个数组我感觉更像是矩阵,官方文档说它是一个多维数组对象(ndarray)。

NumPy provides an N-dimensional array type, the ndarray, which describes a collection of “items” of the same type. The items can be indexed using for example N integers.

Array 的数据结构:

ndarray 是一个通用的同构数据多维容器,数组中的所有元素类型都必须是相同的。每个数组都有一个 shape 和一个 dtype,分别表示数组的维度和元素的数据类型。

创建数组

直接用 np.array()函数将其他序列转换成 ndarray 数组

输入数据可以是列表、元组、数组等序列类型

1
2
3
4
5
import numpy as np

# 列表
a = np.array([1,2,3,4])
a

array([1, 2, 3, 4])

1
2
3
# 元组
b = np.array((1,2,3,4))
b

array([1, 2, 3, 4])

1
2
3
print(np.rank(a))
print(np.ndim(a))
type(a)

1

1

i:\python364\lib\site-packages\ipykernel_launcher.py:1: VisibleDeprecationWarning: rank is deprecated; use the ndim attribute or function instead. To find the rank of a matrix see numpy.linalg.matrix_rank.
“““Entry point for launching an IPython kernel.

numpy.ndarray

  • rank 表示数组的维数
1
a.shape #查看维数,shape 是个元组,表示数组大小

(4,)

这表示是一维向量,但未指定是行向量还是列向量

  • 可以用 reshape 函数指定
1
2
3
a = a.reshape((1,-1)) # 前面的1表示一行,后面的-1是占位符,代表4
a.shape
# 行向量

(1, 4)

1
2
3
a = a.reshape((-1,1))
a.shape
# 列向量

(4, 1)

1
2
3
4
a = a.reshape((2,-1))
print(a)
a.shape
# 两行两列的数组

[[1 2]

[3 4]]

(2, 2)

  • NumPy 的数组索引是从 0 开始的
1
2
3
4
5
6
print(a)
print('--------------')
print(a[1, 1])
print('--------------')
a[1,0] = 88
print(a)

[[1 2][3 4]]


4

[[ 1 2][88 4]]

还可以用这些函数新建数组

  • ones 创建元素为全 1 的数组
  • zeros 创建元素为全 0 的数组
  • full 还可以用 full 函数实现
  • eye/identity 创建单位矩阵
  • empty 创建新数组,只分配内存,但不填充值
  • ones_like 以另一个数组为参数,根据其形状和数据类型创建元素全 1 的数组,类似的有 zeros_like、empty_like
  • random 创建元素值为(0,1)范围的随机值矩阵
1
2
a = np.ones((3,4))
a

array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])

1
2
a = np.zeros((2,3))
a

array([[0., 0., 0.],
[0., 0., 0.]])

1
2
a = np.full((3,4),1)
a

array([[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 1]])

1
2
a =  np.eye(5)
a

array([[1., 0., 0., 0., 0.],
[0., 1., 0., 0., 0.],
[0., 0., 1., 0., 0.],
[0., 0., 0., 1., 0.],
[0., 0., 0., 0., 1.]])

1
2
a =  np.empty((2,3)) # 元素的值是不定值,不是0哦
a

array([[6.17021380e-042, 8.98497795e-067, 8.32380315e-071],
[1.22082702e+165, 3.97948961e-315, 1.89124308e+219]])

1
2
b = np.ones_like(a)
b

array([[1., 1., 1.],
[1., 1., 1.]])

1
2
b = np.zeros_like(a)
b

array([[0., 0., 0.],
[0., 0., 0.]])

1
2
3
a =  np.eye(3)
b = np.empty_like(a)
b

array([[5.97915591e-305, 6.01020264e-305, 5.98054658e-305],
[5.98037274e-305, 5.99198485e-305, 5.98252833e-305],
[5.96785670e-305, 2.15228723e-280, 5.98586589e-305]])

1
2
a =  np.identity(4)
a

array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])

eye identity 都可以创建单位矩阵,除了参数不同之外,二者有何区别?

1
2
a = np.random.random((2,3))
a

array([[0.99217117, 0.26712817, 0.27010174],
[0.30047328, 0.46375106, 0.37945124]])

索引 和切片

数组的索引是从 0 开始的,二维数组的索引如下图:

数组切片是原始数组的视图,也就是说对切片的所有操作都是会影响原数组,视图数据并不是原始数据的拷贝,如果要得到切片的副本,需要显示地进行复制,如 a[2:3].copy()

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

array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

1
a[1][1]

5

1
a[1,1]

5

1
2
b = a[-2,1:3] #取倒数第二行,从第二列开始的两列
print(b,b.shape)# 取出的元素的维数也跟着改变了

5 6

1
2
3
b = a[-1:,1:3]
print(b)
b.shape

[[8 9]]

(1, 2)

1
2
3
b = a[-1,1:3]
print(b)
b.shape

[8 9]

(2,)

结果还是有点不同,索引后的数组不小心的话会出现降维的情况
在 MATLAB 中叫增量运算符,这里有点类似

  • 再看看花式索引(用整数数组进行索引)

和切片不同,它是复制数据到新数组

1
2
3
# 还可以对数组元素批量操作
a = np.arange(32).reshape((8,4))
print(a)

[[ 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]]

1
np.arange(3)

array([0, 1, 2])

1
a[[0,1,2],1]

array([1, 5, 9])

1
2
3
4
a[[1,5,7,2],[0,1,3,2]]


# 这样选取的是行列交叉点的值

array([ 4, 21, 31, 13])

1
2
a[[1,5,7]][:,[0,2,3]]
# 这样选取的是行列矩形区域的值

array([[ 4, 9, 7],
[20, 22, 23],
[28, 30, 31]])

  • 还可以使用 np.ix 函数生成索引器
1
a[np.ix_([1,5,7], [0,2,3])]

array([[ 4, 9, 7],
[20, 22, 23],
[28, 30, 31]])

1
2
3
# 对第二列数据加三
a[np.arange(3),1] += 3
a

array([[ 0, 16, 2, 6],
[ 4, 17, 9, 7],
[ 8, 27, 13, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]])

1
2
3
# 还可以这样写
a[np.arange(3),[1,1,1]] += 3
a

array([[ 0, 19, 2, 6],
[ 4, 20, 9, 7],
[ 8, 30, 13, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]])

1
2
3
# 那这样呢?应该看出规律来了
a[np.arange(3),[1,2,1]] += 3
a

array([[ 0, 22, 2, 6],
[ 4, 20, 12, 7],
[ 8, 33, 13, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]])

1
2
3
<br></br># 还能这样子
a[[0,2,2],[3,2,1]] += 3
a

array([[ 0, 22, 2, 9],
[ 4, 20, 12, 7],
[ 8, 36, 16, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23],
[24, 25, 26, 27],
[28, 29, 30, 31]])

  • 再来看看布尔型索引
1
2
3
# 还可以这样筛选
b = a > 10
b

array([[False, True, False, False],
[False, True, True, False],
[False, True, True, True],
[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True],
[ True, True, True, True]])

1
2
#把这些数取出来
a[b]

array([22, 20, 12, 36, 16, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31])

1
a[a>10]

array([22, 20, 12, 36, 16, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31])

ndarray 的数据类型

可以将整个数组理解成是一整块内存,而 dtype 说明了这块内存的数据类型是什么。

在考虑需要控制数据在内存或磁盘中的存储方式时,了解 dtype 就有必要了

1
2
a = np.array([1,0.2])
a.dtype

dtype(‘float64’)

1
2
a = np.array([1,2,3])
a.dtype

dtype(‘int32’)

1
2
a = np.array([1.3,0.2], dtype=np.int64)
a

array([1, 0], dtype=int64)

1
2
3
a = np.array([1,0.2])
b = np.array(a,np.int64)
b

array([1, 0], dtype=int64)

  • 还可以用 astype 函数转换数据类型
1
2
3
a = np.array([1,0.2])
b = a.astype(np.int64)
b

array([1, 0], dtype=int64)

运算

标量运算

大小相等的数组之间的算术运算,都是元素之间对应相加减乘除,运算单位是数组,批量运算。这点和 MATLAB 也很相似。

  • 加减乘除
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  a = np.array([
    [1,2,3],
    [4,5,6]
])

b = np.array([
    [7,8,9],
    [10,11,0]
])


a + b

array([[ 8, 10, 12],
[14, 16, 6]])

1
np.add(a,b)

array([[ 8, 10, 12],
[14, 16, 6]])

1
a-b

array([[-6, -6, -6],
[-6, -6, 6]])

1
np.subtract(a,b)

array([[-6, -6, -6],
[-6, -6, 6]])

1
a*b

array([[ 7, 16, 27],
[40, 55, 0]])

1
np.multiply(a,b)

array([[ 7, 16, 27],
[40, 55, 0]])

1
a/b

i:\python364\lib\site-packages\ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
“““Entry point for launching an IPython kernel.

array([[0.14285714, 0.25 , 0.33333333],
[0.4 , 0.45454545, inf]])

1
np.divide(a,b)

i:\python364\lib\site-packages\ipykernel_launcher.py:1: RuntimeWarning: divide by zero encountered in true_divide
“““Entry point for launching an IPython kernel.

array([[0.14285714, 0.25 , 0.33333333],
[0.4 , 0.45454545, inf]])

1
1/a

array([[1. , 0.5 , 0.33333333],
[0.25 , 0.2 , 0.16666667]])

1
a ** 0.5

array([[1. , 1.41421356, 1.73205081],
[2. , 2.23606798, 2.44948974]])

除零会有警告

矩阵运算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
a = np.array([
    [1,2,3],
    [4,5,6]
])

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


# 矩阵乘法
a.dot(b)

array([[ 32, 35, 42],
[ 68, 80, 102]])

1
np.dot(a,b)

array([[ 32, 35, 42],
[ 68, 80, 102]])

1
2
3
# 矩阵转置
print(a)
a.T

[[1 2 3][4 5 6]]

array([[1, 4],
[2, 5],
[3, 6]])

1
np.transpose(a)

array([[1, 4],
[2, 5],
[3, 6]])

1
2
# 矩阵的內积
a.dot(a.T)

array([[14, 32],
[32, 77]])

常用函数

  • sqrt()
1
2
# 求根
np.sqrt(a)

array([[1. , 1.41421356, 1.73205081],
[2. , 2.23606798, 2.44948974]])

  • power()
1
2
# 求幂
np.power(a,3)

array([[ 1, 8, 27],
[ 64, 125, 216]], dtype=int32)

1
a

array([[1, 2, 3],
[4, 5, 6]])

  • sum()
1
2
3
# 对所有元素求和

np.sum(a)

21

1
np.sum(a, axis=0) #对没一列元素求和

array([5, 7, 9])

1
np.sum(a, axis=1) #对没一行元素求和

array([ 6, 15])

1
2
# 类似的还有mean函数
np.mean(a)

3.5

  • mean()
1
np.mean(a,axis=0)

array([2.5, 3.5, 4.5])

1
np.mean(a,axis=1)

array([2., 5.])

  • uniform()
1
np.random.random() #(0,1)之内

0.7807819763980599

1
np.random.uniform(50,70) # 指定范围

61.458442560252266

1
np.random.uniform(50,70,(2,3)) #指定维数

array([[59.88385648, 50.59647127, 60.02660835],
[55.62225737, 54.49158301, 68.14669151]])

  • tile
1
a

array([[1, 2, 3],
[4, 5, 6]])

1
np.tile(a,(1,3)) #将a作为结构元素,重复3次,形成新数组

array([[1, 2, 3, 1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6, 4, 5, 6]])

1
np.tile(a,(3,1))

array([[1, 2, 3],
[4, 5, 6],
[1, 2, 3],
[4, 5, 6],
[1, 2, 3],
[4, 5, 6]])

1
np.tile(a,(3,4))

array([[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6],
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6],
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6, 4, 5, 6, 4, 5, 6]])

  • argsort()
1
2
3
4
5
a = np.array([[100, 20, 300],
       [400, 110, 60]])


np.argsort(a) # 默认是进行行排序,返回数组索引

array([[1, 0, 2],
[2, 1, 0]], dtype=int32)

1
np.argsort(a,axis=0)

array([[0, 0, 1],
[1, 1, 0]], dtype=int32)

广播

不同大小数组之间的运算叫广播

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<br></br>a = np.array([
    [1,2,3],
    [4,5,7],
    [8,55,88],
    [3,54,99]
])


b = np.array([
    [8,8,8]
])

如果要实现 a 的每一行的元素都加上 b,可以这么做:

1
2
3
4
# 循环
for i in range(4):
    a[i:i+1,:] += b #注意维数要一致
a

array([[ 9, 10, 11], [ 12, 13, 15], [ 16, 63, 96], [ 11, 62, 107]])

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 用tile函数
a += np.tile(b,(4,1))
a
···

array([[ 17,  18,  19],
       [ 20,  21,  23],
       [ 24,  71, 104],
       [ 19,  70, 115]])

​```python
# 其实可以直接这么做
a + b

array([[ 25, 26, 27],
[ 28, 29, 31],
[ 32, 79, 112],
[ 27, 78, 123]])

不同维数的数组可以直接操作

这就是 numpy 数组的广播特性