3. Numpy数组基础

  • 数组操作是numpy的核心
  • 数组跟list类似,但数组要求内容类型必须一致,如果不一致需要进行强制转换,可能会损失精度
  • 主要包括
    • 数组的属性
    • 数组的索引
    • 数组的切分
    • 数组的编写
    • 数组的拼接和分裂

3.1. 数组的属性

  • numpy可以有多维数组
  • 属性包括:
    • 维度(ndim): 比如一维二维三维数据等
    • 形状(shape):通常可以理解成数组各个维度的长度,比如一个为二维三行四列数组表示为形状(3,4)
    • 长度(size): 数组所存储的元素的总个数, 对于二维数组size=行数x列数
    • 数据类型(dtype): 数组的数据类型(数组要求数据必须同一类型)
    • itemsize: 每个元素的字节长度,比如采用int64的类型,则就是8bytes
    • nbytes: nbytes=itemsize x size
import numpy as np

# 生成三个数组
a1 = np.random.randint(100, size=10) # 参数siz是生成数组的shape属性
a2 = np.random.randint(100, size=(4,6))
a3 = np.random.randint(100, size=(2,5,6))

# 打印出数组a1, a2, a3的属性
for a in [a1, a2, a3]:
    print("dtype={}, ndim={}, shape={}, size={}, itemsize={}, nbytes={}".\
          format(a.dtype, a.ndim, a.shape, a.size, a.itemsize, a.nbytes))
dtype=int32, ndim=1, shape=(10,), size=10, itemsize=4, nbytes=40
dtype=int32, ndim=2, shape=(4, 6), size=24, itemsize=4, nbytes=96
dtype=int32, ndim=3, shape=(2, 5, 6), size=60, itemsize=4, nbytes=240

3.2. 数组的创建

索引的创建我们以后会逐步讲解,这里先介绍几个特殊数组的创建方法。

import numpy as np

# 空数组,此时还是有一些微小的值的
x = np.empty(5)
print("Empty array: \n", x)

# 全为0的数组
x = np.zeros(10)
print("Zeros Array: \n", x)


x = np.ones(10)
print("Ones Array: \n", x)
Empty array: 
 [4.44389806e+252 2.32159998e-152 2.66064968e-260 1.27661731e-152
 6.01334659e-154]
Zeros Array: 
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
Ones Array: 
 [1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

3.3. 数组的索引

  • 数组的索引使用方式跟list一样
  • 可以是正数,负数
# 一位数组的索引
a1 = np.random.randint(100, size=10)
print('a1=', a1)

# 数组的索引跟list一致,分正负数,意义和用法都与list一致
print("a1[1]=", a1[1])
print("a1[-1]=", a1[-1])
a1= [64 90  7 24 78  0 26 25 55 29]
a1[1]= 90
a1[-1]= 29
# 多维数组需要使用逗号隔开,其余使用方法和list类似
# 创建一个shape=(3,5)的数组,此处shape使用size参数表示,变态呀
a1 = np.random.randint(100, size=(3,5))
print("a1 = \n", a1)

# # 数组的索引跟list一致,分正负数,意义和用法都与list一致

# 得到二维数组的某一行
print("a1[1] = ", a1[1])
# 或者
print("a1[1] = ", a1[1, :])
# 或者
print("a1[-2] = ", a1[-2])


# 得到二维数组的某一列
print("a1[:,1] = ", a1[:,1])

# 得到二维数组的某一个元素
print("a1[2,2] =" , a1[2,2])
print("a1[-1,-2] = ", a1[-1,-2])
a1 = 
 [[63 49 13 76 67]
 [99 46 85 33 55]
 [56 25 23 82 43]]
a1[1] =  [99 46 85 33 55]
a1[1] =  [99 46 85 33 55]
a1[-2] =  [99 46 85 33 55]
a1[:,1] =  [49 46 25]
a1[2,2] = 23
a1[-1,-2] =  82

3.4. 数组的切片

  • 数组的切片功能和使用同list基本一致
  • 数组的切片产生的是数据的视图而不是副本,即切片后的数据和源数据是一份
  • 基本用法是 x[start : stop : step] 三个参数决定切片的结果,有些可省略
    • start=0是默认值
    • stop=可以省略
    • step=1是默认值

3.4.1. 一维数组的切片

  • 一维数组跟list使用方法一致
# 一维数组的切片跟list一致,包括用法

# 定义一维数组
import numpy as np
a = np.arange(1, 11)
print("a = ", a)

# 获取中间元素
print("a[3:7] = ", a[3:7])

# 获取偶数
print("a[1::2] = ", a[1::2])

# 获取前五个元素
print("a[:5] = ", a[: 5])

# 获取后五个元素
print("a[5:] = ", a[5:])

# 后五个元素倒序
print("a[:-6:-1] = ", a[:-6:-1])
                        
a =  [ 1  2  3  4  5  6  7  8  9 10]
a[3:7] =  [4 5 6 7]
a[1::2] =  [ 2  4  6  8 10]
a[:5] =  [1 2 3 4 5]
a[5:] =  [ 6  7  8  9 10]
a[:-6:-1] =  [10  9  8  7  6]

3.4.2. 多维数组的切片

  • 多维数组的切片跟list逻辑一致
  • 使用方法上可以看作多维的list
  • 多维数组如果只有一组切片,则是按行切片
# 定义多维数组
a = np.random.randint(10, size=(3,5))

#打印出a
print("a = ", "\n", a)
print()

#按行切片
print(a[1:])
print()

# 截取多维数组的一段
print("a[1:4, 1:4] = \n", a[1:4, 1:4])

print()
# step参数也可以是负数,标识逆序
print("a[1:4:-1, 1:4:-1] = ", a[1:4:-1, 1:4:-1])
print()
print("a[1:4:-1, 1:4:-1] = \r\n", a[::-1, ::-1])
a =  
 [[5 7 8 8 8]
 [0 3 7 4 0]
 [0 3 4 6 3]]

[[0 3 7 4 0]
 [0 3 4 6 3]]

a[1:4, 1:4] = 
 [[3 7 4]
 [3 4 6]]

a[1:4:-1, 1:4:-1] =  []

a[1:4:-1, 1:4:-1] = 
 [[3 6 4 3 0]
 [0 4 7 3 0]
 [8 8 8 7 5]]
  • 获取数组的单行或者单列是一种常见操作,可以利用索引和切片技术实现这个功能。
  • 处于语法简洁考虑,空的切片可以省略
# 利用上面
# 定义多维数组
a = np.random.randint(10, size=(3,5))

print("a = \n", a)

print()
# 得到第二行
print("a[1, :] = ", a[1,:])

print()
# 得到倒序的第二列
print("a[::-1, 1] = ", a[::-1, 1])

print()
# 空的切片可以省略
# a[0, :]可以省略
print("a[0] = ", a[0])


a = 
 [[0 4 0 8 9]
 [5 3 1 0 5]
 [0 4 9 8 5]]

a[1, :] =  [5 3 1 0 5]

a[::-1, 1] =  [4 3 4]

a[0] =  [0 4 0 8 9]

3.4.3. 数组切片产生的视图和副本

  • 数组切片和list切片不同之处是,数组切片的结果是源数据的视图而不是副本
  • 如果想要产生源数据的副本,需要用到copy函数
# 证明数组切片产生的是视图而不是副本

# 1. 产生一个数组
a1 = np.zeros(shape=(3,5))
print("a1 = ",a1)

# 2. 切片产生新数组
a2 = a1[1:, 1:4]

# 3, 修改切片后数组内容
a2[1, 2] = 99

# 4. 查看结果
print("a1 = \n", a1)
print("a2 = \n", a2)

# 5. 结果证明,仅仅修改切片后数据,源数据也发生了改变
# 说明,切片仅仅是视图而不是数据的副本
a1 =  [[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
a1 = 
 [[ 0.  0.  0.  0.  0.]
 [ 0.  0.  0.  0.  0.]
 [ 0.  0.  0. 99.  0.]]
a2 = 
 [[ 0.  0.  0.]
 [ 0.  0. 99.]]
# 如果需要产生数据的副本,需要会用到copy功能


# 1. 产生一个数组
a1 = np.zeros(shape=(3,5))
print("a1 = ",a1)

# 2. 切片产生新数组,同时使用copy产生一个数据副本
a2 = a1[1:, 1:4].copy()

# 3, 修改切片后数组内容
a2[1, 2] = 99

# 4. 查看结果
print("a1 = \n", a1)
print("a2 = \n", a2)

a1 =  [[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
a1 = 
 [[0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]
a2 = 
 [[ 0.  0.  0.]
 [ 0.  0. 99.]]

3.5. 数组的变形

  • 变形可以通过reshape来进行操作,前提是必须变形前后长度一致
    • shape(4,5)=>shape(2,10)是可以的
    • shape(4,5)=>shape(4,6)不可以,因为长度不一致
  • 也可以通过newaxis关键字来完成
    • newaxis是一个NoneType的内容,其实就是None
    • 一般用来标识一给新的维度,比如1维的数组想变形成二维的,需要单纯的增加一个维度
    • 比如 shape=(3,) ==> shape=(3,1)
# shape的使用
# 需要注意数组的长度前后必须一致,此处 9=3x3
# 同时需要注意shape的参数是数组,不是几个整数参数
a = np.arange(1,10).reshape((3,3))

print("a = \n", a)
print("a.shape = ", a.shape)
a = 
 [[1 2 3]
 [4 5 6]
 [7 8 9]]
a.shape =  (3, 3)
# newaxis 是单纯的增加一个维度
a = np.arange(10)
print("a = ", a)
print("a.shape = ", a.shape)
print()

# 获得一个行向量
a1 = a[np.newaxis, :]
print("a1 = ", a1)
print("a1.shape = ", a1.shape)

print()
# 获得一个列向量
a2 = a[:, np.newaxis]
print("a2 = \n", a2)
print("a2.shape = ", a2.shape)
a =  [0 1 2 3 4 5 6 7 8 9]
a.shape =  (10,)

a1 =  [[0 1 2 3 4 5 6 7 8 9]]
a1.shape =  (1, 10)

a2 = 
 [[0]
 [1]
 [2]
 [3]
 [4]
 [5]
 [6]
 [7]
 [8]
 [9]]
a2.shape =  (10, 1)

3.6. 数组的拼接和分裂

本节主要研究如何把多个数组拼接成一个数组或者把一个数组拆分成多个数组的情况。

从本节开始需要注意维度的概念或者轴的概念,我们在对数组进行拼接的时候需要明确 沿着哪个轴进行拼接,
以二维数组为例的话: - 竖轴为第一个轴,索引值为0 - 横轴为第二个轴,索引值为1, - 如果默认的拼接,则默认沿着0轴。

3.6.1. 数组的拼接

对数组的拼接一般默认前提是可以进行拼接,比如3x3的数组和5x5的数组拼接就可能存在问题,因为二者需要 拼接的维度是数值不等,此时强行拼接除非进行自动补齐缺失的值。

数组的拼接一般需要指明沿着哪个轴进行拼接,具体实现主要由下面几个函数完成:

  • numpy.concatenate: 可以指明拼接的轴
  • numpy.hstack: 沿着横轴进行拼接
  • numpy.vstack: 沿着竖轴进行拼接
  • numpy.dstack: 沿着第三个轴进行拼接
  • hstack,vstack,dstack看做是concatenate的简写
# concatenate


# 对一维数组的拼接,默认直接追加,只有这一种可能,因为是一维数组呀

# 生成数组a1, a2后将两个数组拼接成a3
# 此处拼接是自己在后面自动追加
a1 = np.arange(5)
a2 = np.arange(5, 10)
a3 = np.concatenate([a1, a2])

# concatenate可以一次性链接好几个数组
a4 = np.concatenate([a1, a2, a3])

print("a1 = ", a1)
print("a2 = ", a2)
print("a3 = ", a3)
print("a4 = ", a4)
a1 =  [0 1 2 3 4]
a2 =  [5 6 7 8 9]
a3 =  [0 1 2 3 4 5 6 7 8 9]
a4 =  [0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9]
# 同样,concatenate可以对多维数组进行拼接
# 拼接的时候,往往需要指出沿着哪个轴或者维度进行拼接

a1 = np.arange(12).reshape((3,4))
print("a1 = \n", a1)

a2 = np.arange(100,112).reshape((3,4))
print("a2 = \n", a2)

# 此时明显看出concatenate默认是沿着第一个轴(index=0)拼接
a3 = np.concatenate([a1, a2])
print("a3 = \n", a3)

# 如果有必要,需要指明进行拼接的轴
# 下面案例沿着第二个轴进行拼接,index=1
a4 = np.concatenate([a1, a2], axis=1)
print("a4 = \n", a4)
a1 = 
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
a2 = 
 [[100 101 102 103]
 [104 105 106 107]
 [108 109 110 111]]
a3 = 
 [[  0   1   2   3]
 [  4   5   6   7]
 [  8   9  10  11]
 [100 101 102 103]
 [104 105 106 107]
 [108 109 110 111]]
a4 = 
 [[  0   1   2   3 100 101 102 103]
 [  4   5   6   7 104 105 106 107]
 [  8   9  10  11 108 109 110 111]]

沿着指定的横轴或者竖轴进行拼接可以直接是红vstack或者hstack,相对简洁,功能一样。

# vstack & hstack
a1 = np.arange(4).reshape((1,4))
a2 = np.arange(100,112).reshape((3,4))
a3 = np.arange(10,13).reshape((3, 1))

# 注意vstack参数的shape
a4 = np.vstack([a1, a2])
print("a4 = \n", a4)

a5 = np.hstack([a2, a3])
print("a5 = \n", a5)
a4 = 
 [[  0   1   2   3]
 [100 101 102 103]
 [104 105 106 107]
 [108 109 110 111]]
a5 = 
 [[100 101 102 103  10]
 [104 105 106 107  11]
 [108 109 110 111  12]]

3.6.2. 数组的分裂

数组的分裂研究的是如何把一个数组分割成多个数组。

主要有一下几个函数完成:

  • numpy.split: 默认沿着axis=0进行分裂,可以指定轴向
  • numpy.hsplit: 沿着横轴进行分裂
  • numpy.vsplit: 沿着竖轴进行分裂
# 生成一个6x8的数组
a1 = np.arange(48).reshape((6,8))
print("a1 = \n", a1)
print("-" * 30)

# 沿着0轴进行分裂成两部分
a2, a3 = np.split(a1, [3])
print("a2 = \n", a2)
print("-" * 30)
print("a3 = \n", a3)
print("-" * 30)

# 沿着0轴分裂成三部分
a4, a5, a6 = np.split(a1, [2,4])
print("a4 = \n", a4)
print("-" * 30)
print("a5 = \n", a5)
print("-" * 30)
print("a6 = \n", a6)
print("-" * 30)

# 沿着1轴分裂成三部分
a7, a8, a9 = np.split(a1, [2,5], axis=1)
print("a7 = \n", a7)
print("-" * 30)
print("a8 = \n", a8)
print("-" * 30)
print("a9 = \n", a9)
a1 = 
 [[ 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]
 [32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47]]
------------------------------
a2 = 
 [[ 0  1  2  3  4  5  6  7]
 [ 8  9 10 11 12 13 14 15]
 [16 17 18 19 20 21 22 23]]
------------------------------
a3 = 
 [[24 25 26 27 28 29 30 31]
 [32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47]]
------------------------------
a4 = 
 [[ 0  1  2  3  4  5  6  7]
 [ 8  9 10 11 12 13 14 15]]
------------------------------
a5 = 
 [[16 17 18 19 20 21 22 23]
 [24 25 26 27 28 29 30 31]]
------------------------------
a6 = 
 [[32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47]]
------------------------------
a7 = 
 [[ 0  1]
 [ 8  9]
 [16 17]
 [24 25]
 [32 33]
 [40 41]]
------------------------------
a8 = 
 [[ 2  3  4]
 [10 11 12]
 [18 19 20]
 [26 27 28]
 [34 35 36]
 [42 43 44]]
------------------------------
a9 = 
 [[ 5  6  7]
 [13 14 15]
 [21 22 23]
 [29 30 31]
 [37 38 39]
 [45 46 47]]
# 类似hstack之类的, vsplit,hsplit,dsplit是沿着0轴,1轴,2轴分裂的缩写

a = np.arange(20).reshape([4,5])
print("a = \n", a)

# vsplit
a1, a2 = np.vsplit(a, [2])
print("a1 = \n", a1)
print("a2 = \n", a2)

# hsplit
a3, a4, a5 = np.hsplit(a, [2,4])
print("a3 = \n", a3)
print("a4 = \n", a4)
print("a5 = \n", a5)
a = 
 [[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]]
a1 = 
 [[0 1 2 3 4]
 [5 6 7 8 9]]
a2 = 
 [[10 11 12 13 14]
 [15 16 17 18 19]]
a3 = 
 [[ 0  1]
 [ 5  6]
 [10 11]
 [15 16]]
a4 = 
 [[ 2  3]
 [ 7  8]
 [12 13]
 [17 18]]
a5 = 
 [[ 4]
 [ 9]
 [14]
 [19]]