Numpy模块详解

一、NumPy是什么?有什么作用?

NumPy 是一个功能强大的 Python 库,主要用于对多维数组执行计算。 NumPy 这个词来源于两个单词-- Numerical 和 Python 。 NumPy 提供了大量的库函数和操作,可以帮助程序员轻松地进行数值计算。在数据分析和机器学习领域被广泛使用。它有以下几个特点:

  • numpy内置了并行运算功能,当系统有多个核心时,做某种计算时,numpy会自动做并行计算。
  • Numpy底层使用C语言编写,内部解除了GIL(全局解释器锁),其对数组的操作速度不受Python解释器的限制,效率远高于纯Python代码。
  • 有一个强大的N维数组对象Array(一种类似于列表的东西)。
  • 实用的线性代数、傅里叶变换和随机数生成函数。

NumPy中文文档: https://www.numpy.org.cn/user/ ,安装如下:

pip install numpy

Numpy 中的数组的使用跟 Python 中的列表非常类似。他们之间的区别如下:

  • 一个列表中可以存储多种数据类型。比如 a = [1,‘sss’] 是允许的,而数组只能存储同种数据类型。
  • 数组可以是多维的,当多维数组中所有的数据都是数值类型的时候,相当于线性代数中的矩阵,是可以进行相互间的运算的。

二、数组的创建

Numpy 经常和数组打交道,因此首先第一步是要学会创建数组。在 Numpy 中的数组的数据类型叫做 ndarray 。数组中的数据类型都是一致的,要么都是整形,要么都是浮点类型,要么都是字符串类型,不能同时出现多种数据类型。

2.1. 列表生成

import numpy as np

a1 = np.array([1, 2, 3, 4])
print(a1) #[1 2 3 4]
print(type(a1)) # <class 'numpy.ndarray'>
print(a1[3]) # 4

2.2.使用 np.arange 生成

np.arange 的用法类似于 Python 中的 range :

# 生成数组,从2-21之间取值,间隔2取一个值
a2 = np.arange(2, 21, 2)
print(a2)

2.3. np.random.random来创建一个N行N列的数组

其中里面的值是0-1之间的随机数

# 生成2行3列的随机数的数组
a3 = np.random.random((2,3))
'''
[[0.15360416 0.19531969 0.65597595]
 [0.72591998 0.62490847 0.09755287]]
'''
print(a3)

2.4.np.random.randint来创建一个N行N列的数组

其中值的范围可以通过前面2个参数来指定

# 创建随机整数数组, 1到10之间获取数字,生成3个数组,每个里面4个数字
random_int_array = np.random.randint(1, 10, (3, 4))
print(random_int_array)
'''
[[7 3 9 5]
 [5 9 8 2]
 [3 9 7 7]]
'''

2.5.使用函数生成特殊的数组

  • zeros
# 生成一个所有元素都是0的 3行4列的数组
array_zeros = np.zeros((3, 4))
print(array_zeros)
'''
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]
'''
  • ones
# 生成一个所有元素都是1的 3行5列的数组
array_ones = np.ones((3, 5))
'''
[[1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]
 [1. 1. 1. 1. 1.]]
'''
  • full
# 生成一个所有元素都是9的 2行3列的数组
array_full = np.full((2,3),9)
print(array_full)
  • eye
# 生成一个在斜方形上元素为1,其他元素都为0的4x4的矩阵
array_eye = np.eye(4)
'''
[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]
'''

三、Numpy数组和Python列表性能对比

测试一下Numpy数组和Python列表性能,比如我们想要对一个Numpy数组和Python列表中的每个数进行求平方

import numpy as np
import time

# Python列表
t1 = time.time()
nums = []
for i in range(1000000):
    nums.append(i**2)
t2 = time.time()
# 统计时间
print(t2-t1) # 0.11628222465515137

# numpy数组
t3 = time.time()
b = np.arange(1000000)**2
t4 = time.time()
print(t4-t3) # 0.011335372924804688

四、数组数据类型dtype

因为数组中只能存储同一种数据类型,因此可以通过 dtype 获取数组中的元素的数据类型。numpy 支持的数据类型比 Python 内置的类型要多很多,基本上可以和 C 语言的数据类型对应上,其中部分类型对应为 Python 内置的类型。下表列举了常用 NumPy 基本类型。

名称描述
bool_ 布尔型数据类型(True 或者 False)
int_ 默认的整数类型(类似于 C 语言中的 long,int32 或 int64)
intc 与 C 的 int 类型一样,一般是 int32 或 int 64
intp 用于索引的整数类型(类似于 C 的 ssize_t,一般情况下仍然是 int32 或 int64)
int8 字节(-128 to 127)
int16 整数(-32768 to 32767)
int32 整数(-2147483648 to 2147483647)
int64 整数(-9223372036854775808 to 9223372036854775807)
uint8 无符号整数(0 to 255)
uint16 无符号整数(0 to 65535)
uint32 无符号整数(0 to 4294967295)
uint64 无符号整数(0 to 18446744073709551615)
float_ float64 类型的简写
float16 半精度浮点数,包括:1 个符号位,5 个指数位,10 个尾数位
float32 单精度浮点数,包括:1 个符号位,8 个指数位,23 个尾数位
float64 双精度浮点数,包括:1 个符号位,11 个指数位,52 个尾数位
complex_ complex128 类型的简写,即 128 位复数
complex64 复数,表示双 32 位浮点数(实数部分和虚数部分)
complex128 复数,表示双 64 位浮点数(实数部分和虚数部分)

numpy 的数值类型实际上是 dtype 对象的实例,并对应唯一的字符,包括 np.bool_,np.int32,np.float32,等等。

我们可以看到, Numpy 中关于数值的类型比 Python 内置的多得多,那为什么Numpy的数组中有这么多的数据类型呢?因为Numpy本身是基于C语言编写的,C语言中本身就是有很多数据类型,所以直接引用过来了。
Numpy为了考虑到处理海量数据的性能,针对不同的数据给不同的数据类型,来节省内存空间,所以有不同的数据类型。这是 Numpy 为了能高效处理处理海量数据而设计的。举个例子,比如现在想要存储上百亿的数字,并且这些数字都不超过254(一个字节内),我们就可以将 dtype 设置为 int8 ,这样就比默认使用 int64 更能节省内存空间了。类型相关的操作如下:

⑴.默认的数据类型:

import numpy as np

a1 = np.array([1, 2, 3])
print(a1.dtype) #int32

注意:

  • 如果是windows系统,默认是int32
  • 如果是mac或者linux系统,则根据系统来

⑵.指定 dtype

import numpy as np

a1 = np.array([1, 2, 3], dtype=np.int64)
print(a1.dtype) # int64

⑶.修改 dtype 

要在NumPy中修改数组的数据类型(dtype),可使用astype()方法。这个方法会返回一个新的数组,其中的元素类型被转换为指定的数据类型。下面是一个示例:

import numpy as np

a1 = np.array([1, 2, 3])
print(a1.dtype) #window系统下默认是int32

# 以下修改dtype
a2 = a1.astype(np.int64) # astype不会修改数组本身,而是会将修改后的结果返回
print(a2.dtype) # int64

五、多维数组的常用属性

5.1.ndarray.size

获取数组中总的元素的个数。如下有个二维数组:

import numpy as np

a1 = np.array([[1, 2, 3], [4, 5, 6]])
print(a1.size) #因为总共有6个元素,输出值为6

5.2.ndarray.ndim

获取数组是几维数组。比如:

import numpy as np

a1 = np.array([1, 2, 3])
print(a1.ndim) #维度为1

a2 = np.array([[1, 2, 3], [4, 5, 6]])
print(a2.ndim) #维度为2

a3 = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]])
print(a3.ndim) #维度为3

5.3.ndarray.shape

ndarray.shape属性用于获取数组的形状信息,例如,对于一个二维数组,shape属性将是一个包含两个元素的元组,分别表示数组的行数和列数。比如以下代码:

import numpy as np

a1 = np.array([1, 2, 3])
print(a1.shape)  # 输出(3,),指的是一维数组,有三个数据

a2 = np.array([[4, 5, 6], [7, 8, 9]])
print(a2.shape)  # 输出(2, 3),指的是二维数字,2行3列

a3 = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print(a3.shape)  # 输出(2, 2, 3),指的是三维数组,共有2个元素,每个元素2列3行、

a4 = np.array([[1, 2, 3], [4, 5]])
print(a4.shape) # 会报错setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (2,) + inhomogeneous part.

5.4.ndarray.reshape

ndarray.reshape() 是 NumPy 中用于改变数组维度的函数。它的作用是将数组重新调整为指定的维度数,而不改变其数据的内容。具体来说,它可以用于将数组变成一个新的形状,或者修改现有数组的维度。

在使用 reshape() 函数时,需要注意以下几点:

⑴.新形状的指定:通过向 reshape() 函数传递一个元组作为参数,元组中的每个元素表示数组在相应维度上的大小。

import numpy as np

a1 = np.array([1, 2, 3, 4, 5, 6])
print(a1)
reshaped_arr = a1.reshape((2, 3)) # 将一维数组变为2*3的二维数组
print(reshaped_arr)

输出

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

这里,原始的一维数组 a1被重新调整为一个 2 行 3 列的二维数组 reshaped_arr

⑵.总元素数量一致:在调整形状时,reshape() 函数要求新形状的总元素数量必须与原始数组的总元素数量一致。否则会抛出 ValueError 异常。

import numpy as np

arr = np.array([1, 2, 3, 4, 5, 6])
try:
    reshaped_arr = arr.reshape((2, 4))
except ValueError as e:
    print(e)

输出:

cannot reshape array of size 6 into shape (2,4)

原因是尝试将包含 6 个元素的一维数组重新调整为一个包含 8 个元素的二维数组,这是不允许的。

⑶.特殊形状指定:可以使用 -1 作为一个特殊的参数值,表示由 NumPy 自动推断该维度的大小。这在需要根据另一些维度的大小来推断某一维度大小时非常有用。

import numpy as np

arr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
reshaped_arr = arr.reshape((2, -1))  # NumPy会自动推断第二个维度的大小
print(reshaped_arr)

输出

[[ 1  2  3  4  5]
 [ 6  7  8  9 10]]

这里,reshape((2, -1)) 将原始数组调整为 2 行,并且在第二个维度上自动计算出 5 列。

注意, reshape 并不会修改原来数组本身,而是会将修改后的结果返回。如果想要直接修改数组本身,那么可以使用 resize 来替代 reshape 。

5.5. ndarray.itemsize

ndarray.itemsize表示数组中每个元素占的大小,单位是字节。比如以下代码:

⑴.获取每个元素的字节大小:

import numpy as np

a1 = np.array([1, 2, 3, 4, 5, 6], dtype=np.int16) # 使用 int16 数据类型
item_size = a1.itemsize
print("Each element occupies {} bytes".format(item_size))

输出:

Each element occupies 2 bytes

这里,a1.itemsize 返回的是数组 arr 中每个元素的字节大小,因为我们指定了 dtype=np.int16,所以每个整数元素占据 2 个字节。

⑵.应用于多维数组:

arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32) # 使用 float32 数据类型
item_size = arr.itemsize
print("Each element occupies {} bytes".format(item_size))

输出:

Each element occupies 4 bytes

这里,arr.itemsize 返回的是数组 arr 中每个元素的字节大小,因为我们指定了 dtype=np.float32,所以每个浮点数元素占据 4 个字节。

注意事项

  • itemsize 属性对于确定数组中元素在内存中的存储方式和大小非常有用。在处理大数据时,了解每个元素的大小可以帮助有效地管理内存和优化计算性能。
  • 默认情况下,NumPy 数组的 itemsize 取决于数组的数据类型 (dtype)。不同的数据类型会占用不同数量的字节,例如 np.int16 占据 2 字节,np.float32 占据 4 字节,np.float64 则占据 8 字节,以此类推。

5.6.总结

前面的函数总结:

  • 数组一般达到3维就已经很复杂了,不太方便计算了,所以我们一般都会把3维以上的数组转换成2维数组来计算。
  • 通过ndarray.ndim可以看到数组的维度。
  • 通过ndarray.shape可以看到数组的形状(几行几列),shape是一个元组,里面有几个元素代表是几维数组。
  • 通过ndarray.reshape可以修改数组的形状。条件只有一个,就是修改后的形状的元素个数必须和原来的个数一致。比如原来是(2,6),那么修改完成后可以变成(3,4),但是不能变成(1,4)。reshape不会修改原来数组的形状,他只会将修改后的结果返回。
  • 通过ndarray.size可以看到数组总共有多少个元素。
  • 通过ndarray.itemsize可以看到数组中每个元素所占内存的大小,单位是字节。(1个字节=8位)。

六、数组索引和切片

6.1.一维数组的索引和切片

import numpy as np

# [0 1 2 3 4 5 6 7 8 9]
a1 = np.arange(10)

# 进行索引操作
print(a1[4]) # 4
# 使用负数索引
print(a1[-1]) # 9

# 进行截取操作
print(a1[4:6]) # [4 5]

# 使用步长
print(a1[::2]) # [0 2 4 6 8]

# 使用步长结合截取
print(a1[5:9:2]) # [5 7]

6.2.多维数组的索引和切片

多维数组的索引和切片也是通过中括号来索引和切片,在中括号中,使用逗号进行分割,逗号前面的是行,逗号后面的是列,如果多维数组中只有一个值,那么这个值就是行。

import numpy as np

arr = np.random.randint(0, 10, size=(4, 6))
print(arr)
print("***************取的第一行数据*************")
print(arr[0])
print("***************取的第二行到第三行数据*************")
print(arr[1:3])

print("***************获取不连续的几行的数据*************")
# arr[[0, 2, 3]] 使用了列表 [0, 2, 3] 作为索引,这意味着它选择了 arr 的第 0、2 和 3 行。
print(arr[[0, 2, 3]])

"""
arr[[1, 3], [2, 4]]拼接规则说明:
    [1, 3] 是行索引。
    [2, 4] 是列索引。
花式索引,取得是(1,3)第1行3列,(2,4)2行4列,注意行和列的索引都是从0开始
"""
print("**************花式索引*************")
print(arr[[1, 3], [2, 4]])

print("**********************************")
# 取1-2行,4-5列
print(arr[1:3, 4:6])

数组索引总结:

  • 如果数组是一维的,那么索引和切片就是跟python的列表是一样的。
  • 如果是多维的(这里以二维为例),那么在中括号中,给两个值,两个值是通过逗号分隔的,逗号前面的是行,逗号后面的是列。如果中括号中只有一个值,那么就是代表的是行。
  • 如果是多维数组(这里以二维为例),那么行的部分和列的部分,都是遵循一维数组的方式,可以使用整形,切片,还可以使用中括号的形式,来代表不连续的。比如a[[1,2],[3,4]],那么返回的就是(1,3),(2,4)的两个值。

6.3.布尔索引

布尔运算也是矢量的,比如以下代码

import numpy as np

# 创建数组,24个数据,4行6列
a1 = np.arange(0, 24).reshape((4, 6))
print(a1)
# 会返回一个新的数组,这个数组中的值全部都是bool类型
print(a1 > 10)

输入如下:

[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]
[[False False False False False False]
 [False False False False False  True]
 [ True  True  True  True  True  True]
 [ True  True  True  True  True  True]]

上面看上去没有什么用,假如现在要实现一个需求,要将 a1 数组中所有小于10的数据全部都提取出来。那么可以使用以下方式实现:

import numpy as np

# 创建数组,24个数据,4行6列
a1 = np.arange(0, 24).reshape((4, 6))
print(a1)

# 求小于10的元素,
a2 = a1 < 10
print(a2)
# 这样就会在a1中把a2中为True的元素对应的位置的值提取出来
print(a1[a2])

其中布尔运算可以有 != 、 == 、 > 、 < 、 >= 、 <= 以及 &(与) 和 |(或) 。示例代码如下:

import numpy as np

# 创建数组,24个数据,4行6列
a1 = np.arange(0, 24).reshape((4, 6))

# 获取数组a1中小于5,或者大于10的元素
a2 = a1[(a1 < 5)|(a1 > 10)]
# 获取的值[ 0  1  2  3  4 11 12 13 14 15 16 17 18 19 20 21 22 23]
print(a2)

6.4.值的替换

利用索引,也可以做一些值的替换。把满足条件的位置的值替换成其他的值。比如以下代码:

import numpy as np

# 创建数组,24个数据,4行6列
a1 = np.arange(0, 24).reshape((4, 6))

# 将第三行的所有值都替换成 12
a1[3] = 12
print(a1)

也可以使用条件索引来实现:

import numpy as np

# 创建数组,24个数据,4行6列
a1 = np.arange(0, 24).reshape((4, 6))

# 将小于5的所有值全部都替换成0
a1[a1 < 5] = 0
print(a1)

也可以使用函数来实现:

import numpy as np

# 创建数组,24个数据,4行6列
a1 = np.arange(0, 24).reshape((4, 6))

# 把a1中所有小于10的数全部变成1,其余的变成0
a2 = np.where(a1 < 10, 1, 0)
print(a2)
"""
[[1 1 1 1 1 1]
 [1 1 1 1 0 0]
 [0 0 0 0 0 0]
 [0 0 0 0 0 0]]
"""

6.5.练习

⑴.将 np.arange(10) 数组中的奇数全部都替换成 -1 。

import numpy as np

# 创建数组,24个数据,4行6列
a1 = np.arange(10)
# 将 np.arange(10) 数组中的奇数全部都替换成 -1 。
a1[a1 % 2 != 0] = -1
# [ 0 -1  2 -1  4 -1  6 -1  8 -1]
print(a1)

⑵.有一个 4 行 4 列的数组(比如: np.random.randint(0,10,size=(4,4)) ),请将其中对角线的数取出来形成一个一维数组。提示(使用 np.eye )。

import numpy as np

# 生成一个 4x4 的二维数组,数组中的元素是 0 到 9 的随机整数。
a1 = np.random.randint(0, 10, size=(4, 4))
print(a1)
# 创建一个 4x4 的单位矩阵(对角线为 True,其他为 False),数据类型为布尔型。这个单位矩阵在布尔索引中用于选择 a1 的对角线元素。
eye = np.eye(4, dtype=bool)
# 使用布尔索引从 a1 中提取对角线位置的元素(即单位矩阵中为 True 的位置)。这一步将生成一个包含 a1 对角线元素的一维数组 a2。
a2 = a1[eye]
print(a2)

注意:使用 dtype=bool,使用布尔类型创建单位矩阵时,矩阵中的元素为 True 和 False,可以直接用于布尔索引。这样在进行布尔索引操作时会更简洁和直接。

⑶.有一个 4 行 4 列的数组,请取出其中 (0,0),(1,2),(3,2) 的点。

import numpy as np

# 生成一个 4x4 的二维数组,数组中的元素是 0 到 9 的随机整数。
a1 = np.random.randint(0, 10, size=(4, 4))
print(a1)

# 多维数组取出其中 (0,0),(1,2),(3,2) 的点
a2 = a1[[0, 1, 3], [0, 2, 2]]
print(a2)

⑷.有一个 4 行 4 列的数组,请取出其中 2-3 行(包括第3行)的所有数据。

import numpy as np

# 生成一个 4x4 的二维数组,数组中的元素是 0 到 9 的随机整数。
a1 = np.random.randint(0, 10, size=(4, 4))
print(a1)

# 有一个 4 行 4 列的数组,请取出其中 2-3 行(包括第3行)的所有数据。
a2 = a1[2:4]
print(a2)

# 或者下面的方式均可
a3 = a1[[2, 3]]
print(a3)

⑸.有一个 8 行 9 列的数组,请将其中1-5行(包含第5行)的第8列大于3的数全部都取出来。

import numpy as np

# 生成一个 8x9 的二维数组,数组中的元素是 0 到 9 的随机整数。
a1 = np.random.randint(0, 10, size=(8, 9))
print(a1)

#从 a1 中选择第 1 行到第 65 行(不包括第 6 行),第 8 列的元素,并将其存储在 nums 变量中。这一步形成了一个一维数组,包含了 a1 中特定列的部分元素。
nums = a1[1:6, 8]
print(nums)

# 创建一个布尔索引 idx,它用于标记 nums 中大于 3 的元素位置。这一步骤利用布尔条件生成一个布尔数组,其中 True 表示对应位置的元素满足条件(大于 3),而 False 表示不满足条件。
idx = nums > 3
print(idx)
# 使用布尔索引 idx 来提取 nums 中满足条件(即 True 值所在位置)的元素,存储在 filtered_nums 变量中。这一步得到了满足条件的元素子集。
filtered_nums = nums[idx]
print(filtered_nums)

七、数组操作

7.1.数组与数的计算

7.1.1.数组的计算

在 Python 列表中,想要对列表中所有的元素都加一个数,要么采用 map 函数,要么循环整个列表进行操作。但是 NumPy 中的数组可以直接在数组上进行操作。示例代码如下:

import numpy as np

# 创建数组 3*4的小数
arr = np.random.random((3, 4))
print(arr)

# 如果想要在数组上所有元素都乘以10,那么可以通过一下方式实现
new_arr = arr*10
print(new_arr)

# 也可以使用round让所有的元素只保留2位小数
round_arr = new_arr.round(2)
print(round_arr)

注意:以上例子是相乘,其实相加、相减、相除也都是类似的。

 

7.1.2.数组中的元素进行四舍五入

np.around() 是 NumPy 库中的一个函数,用于对数组中的元素进行四舍五入操作。其主要作用是将数组中的每个元素按照指定的精度进行四舍五入。这个函数可以处理标量(单个数值)或数组(多维数组),并返回与输入相同形状的数组,但元素的值经过了四舍五入处理。

np.around() 函数的基本语法如下:

np.around(a, decimals=0)

参数说明:

  • a:需要进行四舍五入的输入数据,可以是标量或数组。
  • decimals:指定小数点后的位数,默认为 0,表示四舍五入到最接近的整数。可以是负数,这种情况下会对整数部分进行四舍五入。

示例:

import numpy as np

# 四舍五入到最近的整数
array1 = np.array([1.23, 2.5, 3.67])
rounded1 = np.around(array1)
print(rounded1)  # 输出:[1. 2. 4.]

# 四舍五入到小数点后一位
array2 = np.array([1.234, 2.567, 3.678])
rounded2 = np.around(array2, decimals=1)
print(rounded2)  # 输出: [1.2 2.6 3.7]

# 四舍五入到小数点后两位
array3 = np.array([1.2345, 2.5678, 3.6789])
rounded3 = np.around(array3, decimals=2)
print(rounded3)  # 输出: [1.23 2.57 3.68]

# 四舍五入到十位
array4 = np.array([15, 25, 35])
rounded4 = np.around(array4, decimals=-1)
print(rounded4)  # 输出: [20. 20. 40.]

7.2.数组与数组的计算

7.2.1.结构相同的数组之间的运算

import numpy as np

# 定义两个数组
arr1 = np.arange(0, 24).reshape((4, 6))
arr2 = np.random.randint(1, 10, size=(4, 6))
print(arr1)
print(arr2)

# 相减/相除/相乘都是可以的
arr3 = arr1 + arr2
print(arr3)

输出如下:

7.2.2.与行数相同并且只有1列的数组之间的运算

import numpy as np

arr1 = np.random.randint(10, 20, size=(3, 8))  # 3行8列
arr2 = np.random.randint(1, 10, size=(3, 1))  # 3行1列
print(arr1)
print(arr2)

# 行数相同,且arr2只有一列,能互相运算
arr3 = arr1 - arr2
print(arr3)

输出如下:

7.2.3.与列数相同并且只有1行的数组之间的运算

import numpy as np

arr1 = np.random.randint(10, 20, size=(3, 8))  # 3行8列
arr2 = np.random.randint(1, 10, size=(1, 8))  # 1行8列
print(arr1)
print(arr2)

# 行数相同,且arr2只有一列,能互相运算
arr3 = arr1 - arr2
print(arr3)

输出如下:

7.3. 数组广播机制

7.3.1.广播机制

NumPy 的广播机制是指在进行算术运算时,不同形状的数组之间可以自动地进行形状调整,使得它们能够兼容地进行运算。这种机制可以极大地简化代码,同时提高计算效率。广播机制允许 NumPy 在两个形状不一致的数组上执行逐元素操作,而无需显式地复制数据。

广播机制的基本规则:

  1. 维度对齐:

    • 如果两个数组的维度数不同,则通过在较小数组的前面添加大小为 1 的维度来对齐它们。例如,一个形状为 (5,) 的数组可以视为 (1, 5)
  2. 形状匹配:

    • 在对齐后的形状中,检查每个维度:
      • 如果两个数组在某个维度的长度相同,或者其中一个数组在该维度的长度为 1,则认为它们在该维度上是兼容的。
      • 如果两个数组在某个维度的长度不同且都不为 1,则不能广播,会引发错误。
  3. 扩展数组:

    • 广播机制将较小数组沿着长度为 1 的维度扩展,以匹配较大数组的形状。

7.3.2.具体示例说明

示例 1: 可广播的数组

形状为 (3, 1) 的数组与形状为 (1, 4) 的数组相加

import numpy as np

a = np.array([[1], [2], [3]])  # 形状 (3, 1)
b = np.array([10, 20, 30, 40])  # 形状 (1, 4)

# 广播机制使得 a 和 b 的形状变为 (3, 4)
# a 被广播为 [[1, 1, 1, 1],
#             [2, 2, 2, 2],
#             [3, 3, 3, 3]]
# b 被广播为 [[10, 20, 30, 40],
#             [10, 20, 30, 40],
#             [10, 20, 30, 40]]
result = a + b
print(result)
# 输出:
# [[11 21 31 41]
#  [12 22 32 42]
#  [13 23 33 43]]

广播过程的详细解释:
⑴.原始形状:

  • a 的形状是 (3, 1)
  • b 的形状是 (1, 4)

⑵.维度对齐:

  • a 和 b 都有两个维度,已经对齐,不需要额外添加维度。

⑶.形状匹配:

  • a 在第一个维度的长度是 3,b 在第一个维度的长度是 1,它们是兼容的。
  • a 在第二个维度的长度是 1,b 在第二个维度的长度是 4,它们是兼容的。

⑷.扩展数组:

  • a 被扩展为 [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]
  • b 被扩展为 [[10, 20, 30, 40], [10, 20, 30, 40], [10, 20, 30, 40]]

扩展后,a 和 b 都变为形状 (3, 4),可以进行元素级加法操作。

示例 2: 标量和数组相加
import numpy as np

c = np.array([1, 2, 3])  # 形状 (3,)
d = 10  # 标量被视为形状 ()
# 标量被广播位 [10, 10, 10]
result = c + d
print(result)
# 输出:[11 12 13]
示例 3: 不可广播的数组

形状不兼容的数组,是无法广播的

import numpy as np

arr4 = np.array([[1, 2], [3, 4]])  # 形状 2行2列
arr5 = np.array([1, 2, 3])  # 形状 (3,)
print(arr4)
print(arr5)
# operands could not be broadcast together with shapes (2,2) (3,) 
result = arr4 + arr5
print(result)

不可广播的原因:
⑴.原始形状:

  • arr4 的形状是 (2, 2)
  • arr5 的形状是 (3,)

⑵.维度对齐:

  • arr5 的形状可以视为 (1, 3)。

⑶.形状匹配:

  • arr4 在第一个维度的长度是 2,arr5 在第一个维度的长度是 1,它们是兼容的。
  • arr4 在第二个维度的长度是 2,arr5 在第二个维度的长度是 3,它们不兼容。

由于第二个维度的长度不同且都不为 1,无法广播。

示例4:高维数组之间的广播

[:, np.newaxis] 用于将一维数组 arr2 转换为二维数组,以便与 arr1 进行广播操作。

import numpy as np

arr1 = np.ones((3, 3))  # 形状 3行3列,元素均为1
arr2 = np.array([1, 2, 3])  # 形状 (3,)
print(arr1)
print(arr2)
# 在第二个轴上添加一个新的维度
result = arr1 * arr2[:, np.newaxis]
print(result)
# 输出:
# [[1. 1. 1.]
#  [2. 2. 2.]
#  [3. 3. 3.]]

输出值为:

7.4.数组形状的操作

7.4.1. reshape和resize方法

两个方法都是用来修改数组形状的,但是有一些不同。

reshape 是将数组转换成指定的形状,然后返回转换后的结果,对于原数组的形状是不会发生改变的。调用方式:

import numpy as np

arr1 = np.random.randint(0, 10, size=(3, 4))
print(arr1)
"""
输出:
[[0 0 9 3]
 [0 5 4 4]
 [3 0 2 0]]
"""
# 将修改后的结果返回,不会影响原数组本身
arr2 = arr1.reshape((2, 6))
print(arr2)
"""
输出:
[[0 0 9 3 0 5]
 [4 4 3 0 2 0]]
"""

resize 是将数组转换成指定的形状,会直接修改数组本身。并不会返回任何值。调用方式:

import numpy as np

arr1 = np.random.randint(0, 10, size=(3, 4))
print(arr1)
"""
输出:
[[2 3 3 9]
 [6 4 5 3]
 [9 4 3 1]]
"""
# 将修改后的结果返回,不会影响原数组本身
arr1.resize((2, 6))
print(arr1)
"""
输出:
[[2 3 3 9 6 4]
 [5 3 9 4 3 1]]
"""

7.4.2.flatten和ravel方法

两个方法都是将多维数组转换为一维数组,但是有以下不同:

  • flatten 是将数组转换为一维数组后,然后将这个拷贝返回回去,所以后续对这个返回值进行修改不会影响之前的数组。
  • ravel 是将数组转换为一维数组后,将这个视图(可以理解为引用)返回回去,所以后续对这个返回值进行修改会影响之前的数组。

比如以下代码:

import numpy as np

# 创建一个二维数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
print("原始数组:", arr)

# 使用 flatten 方法,将数组转换为一维数组
flattened = arr.flatten()
print("Flattened array:", flattened)

# 使用 ravel 方法,将数组转换为一维数组
raveled = arr.ravel()
print("Raveled array:", raveled)

# 修改 flattened 数组
flattened[0] = 100
print("原始数组:", arr)
print("Flattened array:", flattened)

# 修改 raveled 数组
raveled[1] = 200  # 对这个raveled返回值进行修改会影响之前的数组
print("原始数组:", arr)  # 返回[[1 200 3] [4 5 6]]
print("Raveled array:", raveled)

7.5.数组(矩阵)转置和轴对换

7.5.1.矩阵转换

numpy 中的数组其实就是线性代数中的矩阵。矩阵是可以进行转置的。为什么要进行矩阵转置呢?有时候在做一些计算的时候需要用到。比如做矩阵的内积的时候,就必须将矩阵进行转置后再乘以之前的矩阵。ndarray 有一个 T 属性,可以返回这个数组的转置的结果。示例代码如下:

import numpy as np

arr1 = np.arange(0, 24).reshape((4, 6))
print(arr1)
print("****************ndarray 有一个 T 属性,可以返回这个数组的转置的结果****************")
arr2 = arr1.T
print(arr2)

"""
输出结果:
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]
****************ndarray 有一个 T 属性,可以返回这个数组的转置的结果****************
[[ 0  6 12 18]
 [ 1  7 13 19]
 [ 2  8 14 20]
 [ 3  9 15 21]
 [ 4 10 16 22]
 [ 5 11 17 23]]
"""

另外还有一个方法叫做 transpose ,这个方法返回的是一个View,也即修改返回值,会影响到原来数组。示例代码如下:

import numpy as np

arr1 = np.arange(0, 24).reshape((4, 6))
print(arr1)
print("****************ndarray 有一个 T 属性,可以返回这个数组的转置的结果****************")
arr2 = arr1.T
print(arr2)
print("****************transpose修改返回值,会影响到原来数组****************")
arr3 = arr1.transpose()  # 效果同a1.T
print(arr3)
"""
输出结果:
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]]
****************ndarray 有一个 T 属性,可以返回这个数组的转置的结果****************
[[ 0  6 12 18]
 [ 1  7 13 19]
 [ 2  8 14 20]
 [ 3  9 15 21]
 [ 4 10 16 22]
 [ 5 11 17 23]]
****************transpose修改返回值,会影响到原来数组****************
[[ 0  6 12 18]
 [ 1  7 13 19]
 [ 2  8 14 20]
 [ 3  9 15 21]
 [ 4 10 16 22]
 [ 5 11 17 23]]
"""

7.5.2.矩阵

NumPy 中包含了一个矩阵库 numpy.matlib,该模块中的函数返回的是一个矩阵,而不是 ndarray 对象。矩阵是一个由行(row)列(column)元素排列成的矩形阵列。numpy.matlib.identity() 函数返回给定大小的单位矩阵(同np.eye())。单位矩阵是个方阵,从左上角到右下角的对角线(称为主对角线)上的元素均为 1,除此以外全都为 0。

矩阵相乘:

numpy.dot(a, b, out=None)

参数:

  • a : ndarray 数组
  • b : ndarray 数组

第一个矩阵第一行的数字(2和1),各自乘以第二个矩阵第一列对应位置的数字(1和1),然后将乘积相加( 2 x 1 + 1 x 1),得到结果矩阵左上角的那个值3。也就是说,结果矩阵第m行与第n列交叉位置的那个值,等于第一个矩阵第m行与第二个矩阵第n列,对应位置的每个值的乘积之和。

# 结果矩阵第m行与第n列交叉位置的那个值,根据m在第一个矩阵取行,根据n在第二个矩阵取列,然后对应位置的每个值的乘积相加。
3=(2*1+1*1)   m=1 n=1
4=(2*2+1*0)   m=1 n=2
7=(4*1+3*1)   m=2 n=1
8=(4*2+3*0)   m=2 n=2

用代码实现就比较简单了:

import numpy as np

arr1 = np.array([[2,1],[4,3]])
print(arr1)
arr2 = np.array([[1,2],[1,0]])
print(arr2)

print("*************矩阵的内积**************")
result = np.dot(arr1,arr2)
print(result)

"""
输出结果:
[[2 1]
 [4 3]]
[[1 2]
 [1 0]]
*************矩阵的内积**************
[[3 4]
 [7 8]]
"""

7.6.不同数组的组合

如果有多个数组想要组合在一起,也可以通过其中的一些函数来实现。

7.6.1.vstack

vstack :将数组按垂直方向进行叠加。数组的列数必须相同才能叠加。示例代码如下:

import numpy as np

arr1 = np.random.randint(0, 10, size=(3, 5))
print(arr1)
arr2 = np.random.randint(0, 10, size=(1, 5))
print(arr2)

print("*************将数组按垂直方向进行叠加**************")
result = np.vstack([arr1, arr2])
print(result)

"""
输出结果:
[[9 1 2 6 8]
 [6 9 1 4 1]
 [2 7 1 6 7]]
[[5 0 5 3 2]]
*************将数组按垂直方向进行叠加**************
[[9 1 2 6 8]
 [6 9 1 4 1]
 [2 7 1 6 7]
 [5 0 5 3 2]]
"""

7.6.2.hstack

hstack :将数组按水平方向进行叠加。数组的行必须相同才能叠加。示例代码如下:

import numpy as np

arr1 = np.random.randint(0, 10, size=(3, 2))
print(arr1)
arr2 = np.random.randint(0, 10, size=(3, 1))
print(arr2)

print("*************将数组按水平方向进行叠加**************")
result = np.hstack([arr1, arr2])
print(result)

"""
输出结果:
[[8 1]
 [4 7]
 [5 8]]
[[9]
 [6]
 [8]]
*************将数组按水平方向进行叠加**************
[[8 1 9]
 [4 7 6]
 [5 8 8]]
"""

7.6.3.concatenate

语法:

concatenate([],axis) 

说明:将两个数组进行叠加,但是具体是按水平方向还是按垂直方向。则要看 axis 的参数,如果 axis=0 ,那么代表的是往垂直方向(行)叠加,如果 axis=1 ,那么代表的是往水平方向(列)上叠加,如果 axis=None ,那么会将两个数组组合成一个一维数组。需要注意的是,如果往水平方向上叠加,那么行必须相同,如果是往垂直方向叠加,那么列必须相同。示例代码如下:

import numpy as np

arr1 = np.array([[1, 2], [3, 4]])
print(arr1)
arr2 = np.array([[5, 6]])
print(arr2)

print("*************将数组按垂直方向进行叠加**************")
result1 = np.concatenate((arr1, arr2), axis=0)
print(result1)

print("*************将数组按水平方向进行叠加**************")
"""
这里需要将 arr2 转置为 arr2.T,因为 arr2 的形状为 (1, 2),而转置后的形状为 (2, 1),这样可以匹配 arr1 (2,2)的行数
如果不进行转置,直接执行 np.concatenate((arr1, arr2), axis=1) 会导致维度不匹配的错误(如果往水平方向上叠加,那么行必须相同),因为形状 (2, 2) 和 (1, 2) 沿 axis=1 拼接时行数不一致。
"""
result2 = np.concatenate((arr1, arr2.T), axis=1)  # arr2.T 即arr2的转置
print(result2)

print("*************将两个数组组合成一个一维数组**************")
result3 = np.concatenate((arr1, arr2), axis=None)
print(result3)
"""
输出结果:
[[1 2]
 [3 4]]
[[5 6]]
*************将数组按垂直方向进行叠加**************
[[1 2]
 [3 4]
 [5 6]]
*************将数组按水平方向进行叠加**************
[[1 2 5]
 [3 4 6]]
*************将两个数组组合成一个一维数组**************
[1 2 3 4 5 6]
"""

7.7.数组的切割

通过 hsplit 和 vsplit 以及 array_split 可以将一个数组进行切割。

7.7.1.hsplit

hsplit :按照水平方向进行切割。用于指定分割成几列,可以使用数字来代表分成几部分,也可以使用数组来代表分割的地方。示例代码如下:

import numpy as np

arr1 = np.arange(16).reshape(4,4)
print(arr1)

print("*************将arr1分割成2部分**************")
arr2 = np.hsplit(arr1,2)
print(arr2)

print("*************下标为1的地方切一刀,下标为2的地方切一刀,分成三部分**************")
arr3 = np.hsplit(arr1,[1, 2])
print(arr3)


"""
输出结果:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
*************将arr1分割成2部分**************
[array([[ 0,  1],
       [ 4,  5],
       [ 8,  9],
       [12, 13]]), array([[ 2,  3],
       [ 6,  7],
       [10, 11],
       [14, 15]])]
*************下标为1的地方切一刀,下标为2的地方切一刀,分成三部分**************
[array([[ 0],
       [ 4],
       [ 8],
       [12]]), array([[ 1],
       [ 5],
       [ 9],
       [13]]), array([[ 2,  3],
       [ 6,  7],
       [10, 11],
       [14, 15]])]
"""

7.7.2.vsplit

vsplit :按照垂直方向进行切割。用于指定分割成几行,可以使用数字来代表分成几部分,也可以使用数组来代表分割的地方。示例代码如下:

import numpy as np

arr1 = np.arange(16).reshape(4,4)
print(arr1)

print("*************将arr1按照行总共分成2个数组**************")
arr2 = np.vsplit(arr1,2)
print(arr2)

print("*************按照行进行划分,在下标为1的地方和下标为2的地方分割,[1,2] 和 (1,2)结果一样**************")
arr3 = np.vsplit(arr1,[1, 2])
print(arr3)

"""
输出结果:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
*************将arr1按照行总共分成2个数组**************
[array([[0, 1, 2, 3],
       [4, 5, 6, 7]]), array([[ 8,  9, 10, 11],
       [12, 13, 14, 15]])]
*************按照行进行划分,在下标为1的地方和下标为2的地方分割,[1,2] 和 (1,2)结果一样**************
[array([[0, 1, 2, 3]]), array([[4, 5, 6, 7]]), array([[ 8,  9, 10, 11],
       [12, 13, 14, 15]])]
"""

7.7.3.split/array_split

split/array_split(array,indicate_or_seciont,axis) :用于指定切割方式,在切割的时候需要指定是按照行还是按照列, axis=1 代表按照列, axis=0 代表按照行。示例代码如下:

import numpy as np

arr1 = np.arange(16).reshape(4, 4)
print(arr1)

print("*************按照垂直方向切割成2部分**************")
arr2 = np.array_split(arr1, 2, axis=0)
print(arr2)

print("*************按照水平方向切割成2部分**************")
arr3 = np.array_split(arr1, 2, axis=1)
print(arr3)

"""
输出结果:
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
*************按照垂直方向切割成2部分**************
[array([[0, 1, 2, 3],
       [4, 5, 6, 7]]), array([[ 8,  9, 10, 11],
       [12, 13, 14, 15]])]
*************按照水平方向切割成2部分**************
[array([[ 0,  1],
       [ 4,  5],
       [ 8,  9],
       [12, 13]]), array([[ 2,  3],
       [ 6,  7],
       [10, 11],
       [14, 15]])]
"""

7.8.常用的统计函数

  • numpy.amin()和 numpy.amax():用于计算数组中的元素沿指定轴的最小、最大值
  • numpy.ptp():计算数组中元素最大值与最小值的差(最大值 - 最小值)
  • numpy.median():用于计算数组 a 中元素的中位数(中值)
  • 标准差std():标准差是一组数据平均值分散程度的一种度量。公式:
std = sqrt(mean((x - x.mean())**2))

说明:如果数组是 [1,2,3,4],则其平均值为 2.5。 因此,差的平方是[2.25,0.25,0.25,2.25],并且其平均值的平方根除以 4,即 sqrt(5/4) ,结果为 1.118033988749895。

# 1.计算平均值
(1+2+3+4)/ 4=2.5

# 2.计算每个元素与平均值的差并平方(其中减去的2.5指的是平均值):
2.25 =(1-2.5)**2
0.25 =(2-2.5)**2
0.25 =(3-2.5)**2
2.25 =(4-2.5)**2

# 3.计算方差:
(2.25+0.25+0.25+2.25)/ 4 = 1.25

# 4.计算标准差(方差的平方根)
1.25平方根约等于1.118
  • 方差var():统计中的方差(样本方差)是每个样本值与全体样本值的平均数之差的平方值的平均数,即 mean((x - x.mean())** 2)。换句话说,标准差是方差的平方根。
# 1.计算平均值
(1+2+3+4)/ 4=2.5

# 2.计算每个元素与平均值的差并平方(其中减去的2.5指的是平均值):
2.25 =(1-2.5)**2
0.25 =(2-2.5)**2
0.25 =(3-2.5)**2
2.25 =(4-2.5)**2

# 3.计算方差:
(2.25+0.25+0.25+2.25)/ 4 = 1.25

示例:

import numpy as np

arr1 = np.array([1, 2, 3, 4])
print(arr1)
print(arr1.ptp())  # (最大值 - 最小值) = (4 - 1) = 3
print(arr1.mean())  # 计算数组 a 中元素的中位数(中值)2.5
print(arr1.std())  # 标准差 1.118033988749895
print(arr1.var())  # 方差 1.25

八、numpy数组的深浅拷贝

在操作数组的时候,它们的数据有时候拷贝进一个新的数组,有时候又不是,这经常使初学者感到困惑。在数组操作中分成三种拷贝:

  • 不拷贝:直接赋值,那么栈区没有拷贝,只是用同一个栈区定义了不同的名称。
  • 浅拷贝:只拷贝栈区,栈区指定的堆区并没有拷贝。
  • 深拷贝:栈区和堆区都拷贝了。

8.1.不拷贝

如果只是简单的赋值,那么不会进行拷贝。示例代码如下:

import numpy as np

arr1 = np.array([1, 2, 3, 4])
# 这种情况不会进行拷贝
arr2 = arr1
# 返回True,说明b和a是相同的
print(arr2 is arr1)

8.2.View或者浅拷贝

有些情况,会进行变量的拷贝,所指向的内存空间都是一样的,那么这种情况叫做浅拷贝,或者叫做 View(视图) 。比如以下代码:

import numpy as np

arr1 = np.array([1, 2, 3, 4])
arr2 = arr1.view()
# 返回False,说明arr1和arr2是两个不同的变量
print(arr2 is arr1)
# 修改值
arr2[0] = 100
# 打印100,说明对arr2的修改,会影响arr1上面的值,说明他们指向的内存空间还是一样的,这种叫做浅拷贝,或者说是view
print(arr1[0])

8.3.深拷贝

将之前数据完完整整的拷贝一份放到另外一块内存空间中,这样就是两个完全不同的值了。示例代码如下:

import numpy as np

arr1 = np.array([1, 2, 3, 4])
arr2 = arr1.copy()
# 返回False,说明arr1和arr2是两个不同的变量
print(arr2 is arr1)
# 修改值
arr2[0] = 100
# 打印1,说明arr2和arr1指向的内存空间完全不同了。
print(arr1[0])

之前用到的ravel 返回的就是View(浅拷贝),而 flatten 返回的就是深拷贝。
注意:这里和python list中的区别是copy直接是深拷贝,两者互不影响

九、文件操作

如果想专门的操作CSV文件,python内置的有一个模块叫做csv,不需要安装,这里不作过多介绍。接下来我们主要讲numpy操作文件。

9.1.文件保存

有时候我们有了一个数组,需要保存到文件中,那么可以使用 np.savetxt 来实现。相关的函数描述如下:

numpy.savetxt(fname, X, fmt='%.18e', delimiter=' ', newline='\n', header='', footer='', comments='# ', encoding=None)

参数说明

  • fname: 文件名或文件对象。可以是字符串或文件对象。如果是字符串,表示要保存到的文件名。
  • X: 需要保存的数组。
  • fmt: 可选。格式字符串,指定数组中每个元素的格式。默认为 '%.18e',表示科学计数法。fmt 参数还可以使用其他格式选项:
    • %f:浮点数(默认保留六位小数)
    • %.2f:浮点数(保留两位小数)
    • %e:科学计数法表示的浮点数
    • %s:字符串
    • %d:保留成整数类型,以十进制整数形式保存到文件
  • delimiter: 可选。分隔符字符串,用于分隔数组中的元素。默认为空格 ' '
  • newline: 可选。定义行结束符,默认为换行符 \n
  • header: 可选。字符串,将被写入到文件的开头。
  • footer: 可选。字符串,将被写入到文件的末尾。
  • comments: 可选。字符串,将被添加到 headerfooter 的开头。默认是 #
  • encoding: 可选。文件的编码方式,默认为 None,表示使用系统默认编码。

⑴.基本使用:

import numpy as np

# 创建一个二维数组
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 将数组保存到文本文件中
np.savetxt('array.txt', arr)

生成文件如下:

⑵.使用自定义分隔符和格式:

import numpy as np

# 创建一个二维数组
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 将数组保存到文本文件中,使用逗号作为分隔符,保留2位小数
np.savetxt('array.txt', arr, delimiter=',', fmt="%.2f")

生成文件如下:

⑶.添加头部和尾部:

import numpy as np

# 创建一个二维数组
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

#  将数组保存到文本文件中,添加头部和尾部
np.savetxt('array.txt', arr, delimiter=',', fmt="%.2f",  header='This is the header', footer='This is the footer', comments='## ')

生成文件如下:

9.2.读取文件

有时候我们的数据是需要从文件中读取出来的,那么可以使用 np.loadtxt 来实现。相关的函数描述如下:

numpy.loadtxt(fname, dtype=<class 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0, encoding='bytes', max_rows=None, like=None)

参数说明

  • fname: 文件名或文件对象。可以是字符串、文件对象或生成器。
  • dtype: 可选。要加载的数据类型。默认是 float
  • comments: 可选。用于标识注释的字符串,默认为 #。所有从该字符串开始的行都将被忽略。
  • delimiter: 可选。用于分隔列的字符串。默认是空格。
  • converters: 可选。字典,用于将列转换为特定数据类型的函数。键是列索引,值是转换函数。
  • skiprows: 可选。跳过文件开头的行数。
  • usecols: 可选。要读取的列索引。可以是整数或整数序列。
  • unpack: 可选。如果为 True,则返回解包后的列数据。
  • ndmin: 可选。指定返回数组的最小维度。
  • encoding: 可选。指定文件的编码类型。默认为 bytes
  • max_rows: 可选。要读取的最大行数。
  • like: 可选。通过传递数组的类,可以生成类似的数组对象。

⑴.基本使用:

import numpy as np

# 创建并保存一个数组到文本文件
arr = np.array([[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]])
np.savetxt('array.txt', arr)

#  从文本文件加载数据
load_arr = np.loadtxt('array.txt')
print(load_arr)
"""
输入:
[[1.1 2.2 3.3]
 [4.4 5.5 6.6]
 [7.7 8.8 9.9]]
"""

⑵.使用自定义分隔符:

import numpy as np

# 创建并保存一个数组到csv文件
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
np.savetxt('array.csv', arr, delimiter=',')

#  从csv文件加载数据
load_arr = np.loadtxt('array.csv', delimiter=',')
print(load_arr)
"""
输入:
[[1. 2. 3.]
 [4. 5. 6.]
 [7. 8. 9.]]
"""

⑶,指定数据类型和跳过行数:

本地有一个students.csv文件,格式是这样的。需要我们读取文件 内容,但是不要加载文件的第一行表头信息

代码如下:

import numpy as np

# 创建学生信息列表
students = [
    ["Name", "Age", "Gender", "Score"],
    ["Alice", 20, "Female", 85.5],
    ["Bob", 21, "Male", 78.0],
    ["Charlie", 22, "Male", 92.0],
    ["David", 20, "Male", 88.5],
    ["Eva", 21, "Female", 91.0]
]

# 将学生信息列表转换为numpy数组
students_array = np.array(students)

# 保存到csv文件
np.savetxt('students.csv', students_array, delimiter=',', fmt='%s')

# 获取文件内容,跳过第一行
loaded_students = np.loadtxt('students.csv', delimiter=',', dtype=str, skiprows=1)
print(loaded_students)

"""
输入:
[['Alice' '20' 'Female' '85.5']
 ['Bob' '21' 'Male' '78.0']
 ['Charlie' '22' 'Male' '92.0']
 ['David' '20' 'Male' '88.5']
 ['Eva' '21' 'Female' '91.0']]
"""

⑷.读取特定列:

import numpy as np

# 从文本文件加载数组,只读取第一列和第三列
loaded_students = np.loadtxt('students.csv', delimiter=',', dtype=str, usecols=(0, 2))
print(loaded_students)

"""
输入:
[['Name' 'Gender']
 ['Alice' 'Female']
 ['Bob' 'Male']
 ['Charlie' 'Male']
 ['David' 'Male']
 ['Eva' 'Female']]
"""

⑸.解包列数据:

import numpy as np

# 从csv文件加载数组,并解包列数据(unpack=True)
name, age, gender, score = np.loadtxt('students.csv', delimiter=',', dtype=str, unpack=True)
print("Column 1:", name)
print("Column 2:", age)
print("Column 3:", gender)
print("Column 3:", score)

"""
输入:
Column 1: ['Name' 'Alice' 'Bob' 'Charlie' 'David' 'Eva']
Column 2: ['Age' '20' '21' '22' '20' '21']
Column 3: ['Gender' 'Female' 'Male' 'Male' 'Male' 'Female']
Column 3: ['Score' '85.5' '78.0' '92.0' '88.5' '91.0']
"""

9.3. numpy独有的存储解决方案

numpy 中还有一种独有的存储解决方案。文件名是以 .npy 或者 npz 结尾的。以下是存储和加载的函数。

  • 存储: np.save(fname,array) 或 np.savez(fname,array) 。其中,前者函数的扩展名是 .npy ,后者的扩展名是 .npz ,后者是经过压缩的。
  • 加载: np.load(fname) 。

9.3.1.使用 .npy 文件存储和加载单个数组

  • 存储单个数组
import numpy as np

# 创建一个数组
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 将数组保存到 .npy 文件
np.save("array.npy", arr)
  • 加载单个数组
import numpy as np

# # 从 .npy 文件加载数组
loaded_arr = np.load("array.npy")
print(loaded_arr)

"""
输入:
[[1 2 3]
 [4 5 6]
 [7 8 9]]
"""

9.3.2.使用 .npz 文件存储和加载多个数组

  • 存储多个数组
import numpy as np

# 创建多个数组
arr1 = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2 = np.array([10, 20, 30])
arr3 = np.array([[1.1, 2.2], [3.3, 4.4]])

# 将多个数组保存到 .npz 文件
np.savez("arrays.npz", arr1=arr1, arr2=arr2, arr3=arr3)
  • 加载多个数组
import numpy as np

# 从 .npz 文件加载多个数组
loaded_data = np.load("arrays.npz")

# 提取打印输出每个数组
loaded_arr1 = loaded_data['array1']
loaded_arr2 = loaded_data['array2']
loaded_arr3 = loaded_data['array3']

print("Array 1:", loaded_arr1)
print("Array 2:", loaded_arr2)
print("Array 3:", loaded_arr3)

"""
输入:
Array 1: [[1 2 3]
 [4 5 6]
 [7 8 9]]
Array 2: [10 20 30]
Array 3: [[1.1 2.2]
 [3.3 4.4]]
"""

总结

  • .npy 文件 用于存储单个数组,存储和读取速度快,文件体积较小。
  • .npz 文件 是一个压缩文件,可以存储多个数组,适用于需要保存和加载多个数组的情况。

这些方法使得处理大规模数据时更加高效和便捷,特别是在科学计算和数据分析领域。

十、数组实操

问题1:数组a = np.random.rand(3,2,3)能和b = np.random.rand(3,2,2)进行运算吗?能和c = np.random.rand(3,1,1)进行运算吗?请说明结果的原因

⑴.a 和 b 不能直接进行运算,这里的 a 和 b 分别是形状为 (3, 2, 3) 和 (3, 2, 2) 的三维数组。

  • a 的形状是 (3, 2, 3),即第一个维度为 3,第二个维度为 2,第三个维度为 3。
  • b 的形状是 (3, 2, 2),即第一个维度为 3,第二个维度为 2,第三个维度为 2。

在 NumPy 的广播机制中,两个数组在每个维度的对应轴上要么具有相同的形状,要么其中一个数组在该维度上的长度为 1,才能进行运算。

  • a 和 b 的最后两个维度分别是 (2, 3) 和 (2, 2),它们在这两个维度上的形状并不匹配,因此不能直接进行逐元素的加法、减法等运算。

因此,a 和 b 不能直接进行运算,因为它们不满足广播的机制要求。

⑵.数组 a 和 c可以进行运算,这里的 a 是形状为 (3, 2, 3) 的数组,而 c 是形状为 (3, 1, 1) 的数组。

  • a 的形状是 (3, 2, 3),即第一个维度为 3,第二个维度为 2,第三个维度为 3。
  • c 的形状是 (3, 1, 1),即第一个维度为 3,第二个维度为 1,第三个维度为 1。

在这种情况下:

  • c 的形状是 (3, 1, 1),可以通过广播规则扩展为 (3, 2, 3),即复制其第二和第三个维度,使得与 a 的形状相匹配。
  • 扩展后,c 变成了一个形状为 (3, 2, 3) 的数组,与 a 的形状相同,因此它们可以进行逐元素的加法、减法等运算。

因此,a 和 c 可以进行运算,因为它们满足广播机制的要求。

问题2:想要将数组a = np.arange(15).reshape(3,5)b=np.arange(100,124).reshape(6,4)叠加在一起,其中ab的上面,并且在b的第1列后面(下标从0开始)新增一列,用0来填充。

import numpy as np

# 1.创建数组
a = np.arange(15).reshape(3, 5)
"""
a输出:
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]
"""
b = np.arange(100, 124).reshape(6, 4)
"""
b输出:
[[100 101 102 103]
 [104 105 106 107]
 [108 109 110 111]
 [112 113 114 115]
 [116 117 118 119]
 [120 121 122 123]]
"""

# 2. 因为b只有4列,无法直接和a堆叠,且题目要求在第1列后面添加一列,所以先将b数组在下标为1的地方切割,然后添加完0数组后再进行拼接
b_split_arr = np.hsplit(b, [2])
print(b_split_arr)
"""
[array([[100, 101],
       [104, 105],
       [108, 109],
       [112, 113],
       [116, 117],
       [120, 121]]), array([[102, 103],
       [106, 107],
       [110, 111],
       [114, 115],
       [118, 119],
       [122, 123]])]
"""

# 3.创建一个班全0的6行1列数组
zero_arr = np.zeros((6, 1))

# 4.将b的前半部分、0、b的后半部分组合在一起形成一个新的数组
c = np.hstack([b_split_arr[0], zero_arr, b_split_arr[1]])

# 5.将a和c新生成的数组进行堆叠(将数组按垂直方向进行叠加)
result = np.vstack([a, c])
print(result)
"""
[[  0.   1.   2.   3.   4.]
 [  5.   6.   7.   8.   9.]
 [ 10.  11.  12.  13.  14.]
 [100. 101.   0. 102. 103.]
 [104. 105.   0. 106. 107.]
 [108. 109.   0. 110. 111.]
 [112. 113.   0. 114. 115.]
 [116. 117.   0. 118. 119.]
 [120. 121.   0. 122. 123.]]
"""

问题3:将数组a = np.random.rand(4,5)扁平化成一维数组,可以使用flatten和ravel,对两者的返回值进行操作,哪个会影响到数组a?对会影响到a数组的那个函数,请说明原因。

ravel会影响原来的数组。原因是因为ravel返回的是一个浅拷贝(视图),虽然在栈中的内存不一样,但是指向的堆区的内存地址还是一样,所以操作其返回值,会影响到原来堆区的值。但是flatten返回的却是一个深拷贝,也即栈区和堆区都进行了拷贝,所以操作其返回值,不会影响到原来堆区的值。

十一、NAN和INF值处理

11.1.NAN和INF

NAN和INF意思:

  • NAN:Not A number,不是一个数字的意思,是属于浮点类型的,所以想要进行数据操作的时候需要注意他的类型。
  • INF: Infinity,代表的是无穷大的意思,也是属于浮点类型。 np.inf 表示正无穷大, -np.inf 表示负无穷大,一般在出现除数为0的时候为无穷大。比如 2/0 。

NAN一些特点:

  • NAN和NAN不相等。比如 np.NAN != np.NAN 这个条件是成立的。
  • NAN和任何值做运算,结果都是NAN。

11.2.缺失值处理

有些时候,特别是从文件中读取数据的时候,经常会出现一些缺失值。缺失值的出现会影响数据的处理。因此我们在做数据分析之前,先要对缺失值进行一些处理。处理的方式有多种,需要根据实际情况来做。一般有两种处理方式:

  • 一种是删除缺失值
  • 另外一种就是用其他值进行替代。

⑴.删除缺失值

有时候,我们想要将数组中的 NAN 删掉,那么我们可以换一种思路,就是只提取不为 NAN 的值。示例代码如下:

  • 删除所有的NAN值
import numpy as np

# 1. 删除所有NAN的值,因为删除了值后数组将不知道该怎么变化,所以会被变成一维数组

# 生成一个形状为 (3, 5) 的随机整数数组,并将其转换为 float32 类型。
data = np.random.randint(0, 10, size=(3, 5)).astype(np.float32)
print(data)
"""
[[7. 8. 4. 2. 1.]
 [6. 1. 9. 2. 4.]
 [0. 4. 0. 9. 9.]]
"""
# 将数组 data 中第一行第二列的元素设为 NAN(Not a Number),这里的 np.nan 是 NumPy 提供的表示非数字的特殊值。
data[0, 1] = np.nan
print(data)
"""
[[ 7. nan  4.  2.  1.]
 [ 6.  1.  9.  2.  4.]
 [ 0.  4.  0.  9.  9.]]
"""

"""
此时的data会没有nan,并且变成一维数组
使用掩码 ~np.isnan(data) 过滤掉数组 data 中的 NAN 值。np.isnan(data) 返回一个布尔数组,其中元素为 True 表示对应位置是 NAN。~ 是取反操作符,
所以 ~np.isnan(data) 的意思是将 NAN 的位置取反,即得到一个掩码数组,其中元素为 True 表示对应位置不是 NAN。这个掩码数组用于索引原始数组 data,从而得到不包含 NAN 值的一维数组。
""" data = data[~np.isnan(data)] print(data) """ [7. 4. 2. 1. 6. 1. 9. 2. 4. 0. 4. 0. 9. 9.] """
  • 删除NAN所在的行
import numpy as np

# 2. 删除NAN所在的行

# 生成一个形状为 (3, 5) 的随机整数数组,并将其转换为 float32 类型。这里的 np.random.randint(0, 10, size=(3, 5)) 生成一个范围在 0 到 9 之间的整数随机数组成的数组,astype(np.float32) 将整数数组转换为 float32 类型。
data = np.random.randint(0, 10, size=(3, 5)).astype(np.float32)
print(data)
"""
[[6. 7. 1. 0. 3.]
 [7. 2. 4. 4. 6.]
 [5. 0. 9. 3. 0.]]
"""
# 使用花式索引将数组 data 中第 0 行第 0 列和第 1 行第 2 列的元素设为 NAN(Not a Number),这里的 np.nan 是 NumPy 提供的表示非数字的特殊值。
data[[0, 1],[0, 2]] = np.nan
print(data)
"""
[[nan  7.  1.  0.  3.]
 [ 7.  2. nan  4.  6.]
 [ 5.  0.  9.  3.  0.]]
"""

# 使用 np.isnan(data) 找出数组 data 中所有 NAN 元素所在的位置,np.where() 返回一个元组,第一个元素是包含 NAN 元素的行索引数组,第二个元素是包含 NAN 元素的列索引数组。通过 [0] 取出行索引数组。
lines = np.where(np.isnan(data))[0]
print(lines)
"""
[0 1]
"""

# 使用delete方法删除指定的行,axis=0表示删除行,lines表示删除的行号
result = np.delete(data, lines, axis=0)
print(result)
"""
[[5. 0. 9. 3. 0.]]
"""

⑵.用其他值进行替代

有些时候我们不想直接删掉,比如有一个成绩表,分别是数学和英语,但是因为某个人在某个科目上没有成绩,那么此时就会出现NAN的情况,这时候就不能直接删掉了,就可以使用某些值进行替代。假如有以下表格:

处理代码如下:

import numpy as np

# 加载数据
scores = np.loadtxt("scores.csv", delimiter=",", skiprows=1, encoding="utf-8", dtype=np.str_)
print(scores)

# 找到所有的 'NAN' 并替换为 'nan'
scores[scores == "NAN"] = np.nan
print(scores)

# 将姓名和成绩进行分割
scores_split = np.hsplit(scores, [1])
print(scores_split)

# 将成绩部分进行转换
scores_float = scores_split[1].astype(np.float32)
print(scores_float)
"""
[[85. 92. 88.]
 [78. nan 85.]
 [nan 88. 91.]
 [90. 85. nan]
 [76. 89. 90.]]
"""

# 将成绩为nan的转换为0
scores_float[np.isnan(scores_float)] = 0
print(scores_float)

# 1.求出学生成绩的总分
# 除了delete用axis=0表示行以外,其他的大部分函数都是axis=1来表示行。
sum_score = scores_float.sum(axis=1).reshape(-1, 1) #  使用 reshape 方法将一维数组修改为多行一列
print(sum_score)
# 将数组按水平方向进行叠加。数组的行必须相同才能叠加
arr_result = np.hstack([scores_float, sum_score])
print(arr_result)

# 2.求出每门成绩的平均分
"""
也可以这样写:
# scores_float.shape[1] 是用于获取 NumPy 数组 scores_float 的第二个维度的大小,也就是列出3
# for x in range(scores_float.shape[1]):
#     col = scores_float[:, x]  # 通过遍历 scores_float 数组的每一列,: 表示选择该维度的所有元素。x 是列索引,col 是当前列的数组。
#     non_nan_col = col[~np.isnan(col)]  # 使用布尔索引 ~np.isnan(col) 获取当前列中非 NAN 的元素(也就是数字),生成一个新的数组 non_nan_col,其中只包含非 NAN 的值。
#     mean = non_nan_col.mean()  # 成绩的平均值,并将其存储在 mean 变量中。
"""
# 求每门成绩的平均值
avg_score = arr_result.mean(axis=0)
print(avg_score)
# 平均值(将数组按垂直方向进行叠加。数组的列数必须相同才能叠加)
result2 = np.vstack([arr_result, avg_score])
"""
[[ 85.   92.   88.  265. ]
 [ 78.    0.   85.  163. ]
 [  0.   88.   91.  179. ]
 [ 90.   85.    0.  175. ]
 [ 76.   89.   90.  255. ]
 [ 65.8  70.8  70.8 207.4]]
"""

# 在姓名列添加一个元素,
# 1.要添加的新元素
new_name = np.array([['平均成绩']])
# 2.使用 vstack 方法垂直堆叠数组
names = np.vstack([scores_split[0], new_name])
"""
[['小明']
 ['小红']
 ['小刚']
 ['小华']
 ['小丽']
 ['平均成绩']]
"""

# 组合拼接
new_arr = np.hstack([names, result2])
print(new_arr)

# 保存结果到csv文件
np.savetxt('scores_arr.csv', new_arr, delimiter=',', fmt='%s', encoding="utf-8", header='姓名,数学,英语,科学,总成绩')

生成文件如下:

11.3.NAN和INF总结

  • NAN:Not A Number的简写,不是一个数字,但是他是属于浮点类型。
  • INF:无穷大,在除数为0的情况下会出现INF。
  • NAN和所有的值进行计算结果都是等于NAN
  • NAN != NAN
  • 可以通过np.isnan来判断某个值是不是NAN。
  • 处理值的时候,可以通过删除NAN的形式进行处理,也可以通过值的替换进行处理。
  • np.delete比较特殊,通过axis=0来代表行,而其他大部分函数是通过axis=1来代表行
posted @ 2024-05-24 18:10  酒剑仙*  阅读(17)  评论(0编辑  收藏  举报