跳过正文
  1. 文档/
  2. 【科学计算】NumPy及其基本使用/

1.2 Numpy数组运算

·5934 字·12 分钟· loading · ·
科学计算 Study Python
目录
【科学计算】NumPy及其基本使用 - 这篇文章属于一个选集。
§ 3: 本文

1.2.1 向量的运算
#

对元素的操作
#

如果要进行逐元素的运算就可能用到循环,和列表类似,可以使用 for 循环来遍历一个 numpy.array

import numpy as np

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

for i in arr:
    print(i)

但是for 循环的效率较低,Numpy 提供了更高效的方式来进行数组运算。

  • 第一个是在上界提到过的。numpy.arange([start, ]stop, [step, ], dtype=None) 函数可以生成一个等差数列的数组,他返回的是数组而不是迭代器或列表。
arr = np.arange(0,10,2)
print(arr)
# 输出 [0 2 4 6 8]
  • 第二个方法是numpy.linspace(start,stop,num=50,endpoint=True,retstep=False,dtype=None,axis=0),它可以生成一个等差数列的数组,和arange不同的是,它可以指定生成的元素个数。它是基于间隔数量来生成的。
arr = np.linspace(0, 10, 5) #在0到10之间生成5个数
print(arr)
# 输出 [ 0.   2.5  5.   7.5 10. ]

numpy.arangenumpy.linspacerange 之间的用法存在一些互通之处,但也有不同点。它们都可以用来生成一系列的数字。以下是它们的主要区别:

  • 类型

    • range 返回的是一个迭代器。
    • numpy.arangenumpy.linspace 返回的是 Numpy 数组。
  • 用途

    • range 主要用于 Python 的 for 循环中迭代。
    • numpy.arangenumpy.linspace 主要用于 Numpy 的数值计算,特别是在需要数组操作时。
  • 参数

    • rangenumpy.arange 基于起始值、结束值和步长生成序列。
    • numpy.linspace 基于起始值、结束值和元素数量生成等差数列。
  • 性能

    • 在处理大型数据集时,Numpy 数组(由 numpy.arangenumpy.linspace 生成)比 Python 列表(range 转换为列表)或迭代器(直接使用 range)在性能上有显著优势。
    • 这是因为 Numpy 数组在内存中连续存储,并且支持向量化操作。

向量的线性运算
#

通过 Numpy 进行向量的加减法和缩放运算。

对于任意两个维度相同的向量 \( \vec{a} = (a_1, a_2, \ldots, a_n) \) 和 \( \vec{b} = (b_1, b_2, \ldots, b_n) \),他们的加法及减法定义为: $$\vec{a} + \vec{b} = (a_1 + b_1, a_2 + b_2, \ldots, a_n + b_n)$$ $$\vec{a} - \vec{b} = (a_1 - b_1, a_2 - b_2, \ldots, a_n - b_n)$$

向量的缩放运算涉及一个标量和一个向量,定义为: $$ k \vec{a} = (k a_1, k a_2, \ldots, k a_n) $$

对于更高维度的向量,Numpy 的处理方式与上述相同,只需要确保在进行运算时涉及的向量维度相同即可。

# 定义两个四维向量
vec_c = np.array([1, 2, 3, 4])
vec_d = np.array([5, 6, 7, 8])

# 向量加法
vec_sum_4d = vec_c + vec_d
print("四维向量加法结果:", vec_sum_4d)  # 输出: 四维向量加法结果: [6 8 10 12]

# 向量缩放(以标量3为例)
scaled_vec_4d = 3 * vec_c
print("四维向量缩放结果(乘以3):", scaled_vec_4d)  # 输出: 四维向量缩放结果(乘以3): [3 6 9 12]

向量的非线性运算
#

向量的加减法和数乘都是比较基础的线性运算,但向量还有一些非线性运算方法。**向量的数量积(又称点积、内积或标量积)**是一种特殊的向量运算,其结果是一个标量,而非向量。 向量的数量积定义为两个向量的大小、之间夹角的余弦值的乘积。 $$\vec{a} \cdot \vec{b} = |\vec{a}| \times |\vec{b}| \times \cos\theta$$

数量积有一些重要的性质,例如:

  • 当两个向量同向时,数量积为正,反向为负,垂直时为 0
  • 数量积满足交换律,即\(\vec{a} \cdot \vec{b} = \vec{b} \cdot \vec{a}\)
  • 数量积的绝对值不超过两个向量模的乘积,即\(|\vec{a} \cdot \vec{b}| \leq |\vec{a}| \times |\vec{b}|\)

在 Numpy 中实现向量的数量积,可以使用numpy.dot()函数或者@操作符,这两个操作在处理一维数组时,会计算他们的点积。

import numpy as np

# 定义两个向量
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 计算数量积
dot_product = np.dot(a, b)
# dot_product = a @ b

print("数量积为:", dot_product)  # 输出: 数量积为: 32

注意这里使用乘号的话其实是逐元素乘法,而不是数量积。

另一种非线性运算就是叉乘,向量的叉乘(Cross Product)也被称为向量的叉积或向量积,涉及到两个三维向量的运算,其结果是一个新的向量,这个新向量垂直于原来两个向量所构成的平面,定义为:

  • 对于两个三维向量\(\vec{a} = (a_1, a_2, a_3)\)和\(\vec{b} = (b_1, b_2, b_3)\),他们的叉乘\(\vec{c} = \vec{a} \times \vec{b}\)是一个新的向量,其方向垂直于前两者所在的平面,并且满足右手定则(四指由\(\vec{a}\)指向\(\vec{b}\)的方向,拇指指向\(\vec{c}\)的方向)。

  • 坐标表示,在三维空间中,叉乘的坐标可以通过行列式的方式计算,具体为:

    $$ \begin{array}{c} \vec{c} = \vec{a} \times \vec{b} = \begin{vmatrix} \mathbf{i} & \mathbf{j} & \mathbf{k} \\ a_1 & a_2 & a_3 \\ b_1 & b_2 & b_3 \end{vmatrix} = (a_2b_3 - a_3b_2, a_3b_1 - a_1b_3, a_1b_2 - a_2b_1) \end{array} $$

    其中 \(\mathbf{i}, \mathbf{j}, \mathbf{k}\) 是三维空间的单位向量。

  • 性质:

    • 叉乘不满足交换律,即 \(\vec{a} \times \vec{b} \neq \vec{b} \times \vec{a}\),而是 \(\vec{a} \times \vec{b} = -(\vec{b} \times \vec{a})\)。

    • 叉乘的模等于两向量模的乘积与他们之间夹角的正弦值的积,即 \(|\vec{a} \times \vec{b}| = |\vec{a}| \cdot |\vec{b}| \cdot \sin\theta\)

    • 叉乘的结果垂直于原来两个向量所构成的平面

在 Numpy 库中可以直接使用 numpy.cross()函数来计算两个三维元素的叉乘,这个函数接受两个一维数组(代表三维向量)作为输入,并返回一个一维数组(代表叉乘结果向量)。

import numpy as np

# 定义两个三维向量
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])

# 计算叉乘
c = np.cross(a, b)

# 输出结果
print("叉乘结果:", c)  # 输出类似于:[ -3  6 -3]

此处需要注意:

  • 叉乘是三维向量特有的运算,不适用于二维向量或者更高维度的向量
  • 在使用 Numpy 进行叉乘计算时,需要保证输入的向量是一维数组,并且长度为 3

在 Numpy 中,对数组进行排序、筛选是很常见的操作,Numpy 提供了numpy.sort函数和数组的sort()方法对数组进行排序,numpy.sort()返回排序后的数组副本,而后者则直接对数组本身进行原地排序。

import numpy as np

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

# 使用numpy.sort()排序,返回新数组
sorted_arr = np.sort(arr)
print("Sorted array with numpy.sort:", sorted_arr)

# 使用数组的sort()方法排序,原地修改
arr.sort()
print("Array sorted in-place:", arr)

向量的逻辑运算 - 筛选
#

筛选通常指基于某些条件选择出数组中的元素,在 Numpy 中,可以通过布尔索引、np.where()、以及使用条件表达式等来实现。

  • 单条件筛选

    arr = np.array([1, 2, 3, 4, 5, 6])
    
    # 使用布尔索引筛选出大于3的元素
    filtered_arr = arr[arr > 3]
    print("筛选出大于3的元素:", filtered_arr)
    # 输出: 筛选出大于3的元素: [4 5 6]
    
  • 多条件筛选 对于多条件来说,可以使用逻辑运算符(如&|)来组合多个条件。注意,在使用逻辑运算符时需要确保每个条件都被包裹在括号中,并且可能需要使用numpy.logical_and()numpy.logical_or()来进行逐元素的逻辑运算。

      # 创建一个二维数组
      arr_2d = np.array(
          [[1, 2, 3],
          [4, 5, 6],
          [7, 8, 9]]
      )
    
      # 筛选所有行,其中第一个元素大于3且第二个元素小于8
      # 使用&和括号
      filtered_rows = arr_2d[
          (arr_2d[:,0]>3)&
          (arr_2d[:,1]<8)
      ]
      print(filtered_rows)  # 输出: [[4 5 6]]
    
      # 使用numpy的where函数筛选
      filtered_rows_where = arr_2d[
          np.where((arr_2d[:,0]>3) &
          (arr_2d[:,1]<8))
      ]
      print(filtered_rows_where)  # 输出: [[4 5 6]]
    
      # 使用numpy.logical_and函数筛选
      filtered_rows_logical_and = arr_2d[
          np.logical_and(arr_2d[:,0]>3, arr_2d[:,1]<8)
      ]
      print(filtered_rows_logical_and)  # 输出: [[4 5 6]]
    

1.2.2 矩阵的运算
#

矩阵的算数运算
#

矩阵的加减法类似于向量的加减法,对于任意两个维度相同的矩阵 \(A\) 和 \(B\),他们的加法和减法定义为: 设 A 和 B 是两个同型矩阵(即行数和列数分别相等),则矩阵 A 与 B 的和(或差)是一个与 A、B 同型的矩阵,记为 A+B(或 A-B),其元素由 A 和 B 对应元素相加(或相减)得到。

  • 加法交换律 $$A + B = B + A$$
  • 加法结合律 $$(A + B) + C = A + (B + C)$$
  • 零矩阵和加法恒等元 $$A + O = A = O + A$$
  • 负矩阵和减法逆元 $$A - B = A + (-B)$$

矩阵的数乘:设 k 是一个数,A 是一个矩阵,则数 k 与矩阵 A 的数乘是一个新的矩阵,记为 kA 或 Ak,其元素由 A 中每个元素乘以 k 得到。

  • 结合律 $$kl(A) = k(lA)$$
  • 分配律 $$k(A + B) = kA + kB$$
  • 数乘恒等元 $$1A = A$$
  • 零数乘 $$0A = O$$

矩阵的乘法:A 的队员数量(行数)得和 B 的列数相匹配,这样他们才能手牵手。如果 A 有 m 行 n 列,B 有 n 行 p 列,那么他们跳完舞后形成的新队形 C 就会有 m 行 p 列。 而在 numpy 中,只需使用*(但是这会进行逐元素乘法)或者。所以一般用@或者numpy.matmul()函数来实现矩阵的乘法。

import numpy as np

# 创建两个矩阵A和B
# 假设A是2x3的矩阵,B是3x2的矩阵,这样它们就可以相乘了
A = np.array([[1, 2, 3],
              [4, 5, 6]])
B = np.array([[7, 8],
              [9, 10],
              [11, 12]])

# 使用@符号进行矩阵乘法
C = A @ B
# 或者使用numpy.matmul()函数,效果一样
# C = np.matmul(A, B)

print(C)
# 输出将会是:
# [[ 58  64]
#  [139 154]]
# 这就是A和B“跳舞”后的新队形C

矩阵的逻辑运算 - 查找
#

当然了,在矩阵这种多维形式的数组下,除了执行算术运算,也可以进行增删改查这些操作。多维数组的筛选与一维数组类似,但你可以使用更复杂的条件,并且可以利用多维索引。在上面的例子中,我们已经看到了如何使用[:, 0][:, 1]来索引二维数组的行和列。

# 假设我们有一个表示学生分数的二维数组
scores = np.array([[85, 92], [78, 85], [90, 95], [55, 60]])

# 筛选数学成绩(第二列)大于80的所有行
high_math_scores = scores[scores[:, 1] > 80]
print("Students with high math scores:", high_math_scores)

numpy.where()可以根据条件表达式返回满足条件的元素索引或者满足条件的元素本身。

np.where(condition, [x, y]) 函数有三个参数,但通常我们只使用前两个参数:

  • condition:条件表达式,返回一个布尔数组。
  • [x, y](可选):如果提供了这两个参数,np.where()将返回两个数组,第一个数组包含 conditionTrue 的元素的索引,第二个数组包含 conditionFalse 的元素的索引。如果只提供了 condition 参数,则 np.where()仅返回满足条件的元素的索引。
import numpy as np

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

# 使用 np.where() 找到所有大于3的元素的索引
indices = np.where(arr > 3)
print("Indices of elements greater than 3:", indices)

# 如果你想要基于条件获取实际的元素值
filtered_values = arr[arr > 3]
print("Values greater than 3:", filtered_values)

# 如果你想要同时获取True和False条件下的索引(尽管这在这个场景下可能不常见)
true_indices, false_indices = np.where(arr > 3, [True_indices, False_indices])
# 注意:上面的代码实际上不会按预期工作,因为np.where()不是设计来这样使用的。
# 正确的做法是仅使用condition参数,或者如果你有x和y数组,则使用它们来根据条件替换值。

# 一个更实际的例子,使用x和y来替换值
arr_replaced = np.where(arr > 3, 'High', 'Low')
print("Replaced array with 'High' and 'Low':", arr_replaced)
# 注意:这里arr_replaced的类型将是'object',因为NumPy无法推断出所有元素都是字符串

# 对于多维数组
arr_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 找到所有第二列大于5的元素的行索引和列索引
rows, cols = np.where(arr_2d[:, 1] > 5)
print("Rows and cols where second column > 5:", rows, cols)

注意,在多维数组的示例中,np.where()返回了两个数组:第一个数组包含满足条件的元素的行索引,第二个数组包含列索引。这是因为 np.where()总是返回一个元组,其元素数量与条件数组中的维度数量相匹配。

矩阵上的统计
#

Numpy 还提供了丰富的函数来计算数组的统计指标,包括平均值(mean())、和(sum())、最大值(max())、最小值(min())、标准差(std())、累积和(cumsum())、累计积(cumprod())、连乘积(prod())、方差(var())、中位数(median())、百分位数(percentile())、四分位数(quartile())等。这些函数可以直接应用于数组或矩阵,并且可以指定轴(axis)参数来计算沿特定轴的统计量。

假设我们有一个二维数组 arr,表示某个班级三位同学的三门课成绩:

import numpy as np

# 创建一个二维数组,表示三位同学的三门课成绩
arr = np.array([[85, 92, 88],
                [78, 90, 85],
                [90, 87, 92]])

# 计算平均值
mean_value = np.mean(arr)
print("平均值:", mean_value)
# 输出: 平均值: 87.67

#计算每门课的平均值
mean_per_subject = np.mean(arr, axis=0)
print("每门课的平均值:", mean_per_subject)
# 输出: 每门课的平均值: [84.33 89.67 88.33]

# 计算总和
sum_value = np.sum(arr)
print("总和:", sum_value)
# 输出: 总和: 263

# 计算每门课的总和
sum_per_subject = np.sum(arr, axis=0)
print("每门课的总和:", sum_per_subject)
# 输出: 每门课的总和: [253 269 265]

# 计算累计和
cumsum_value = np.cumsum(arr, axis=1)
print("按行的累计和:\n", cumsum_value)
# 输出:
# [[ 85 177 265]
#  [ 78 168 253]
#  [ 90 177 269]]

# 计算连乘积
prod_all = np.prod(arr)
print("所有成绩的连乘积:", prod_all)
# 输出: 所有成绩的连乘积: 5217120

#计算每位同学总成绩连乘积
prod_per_student = np.prod(arr, axis=1)
print("每位同学总成绩的连乘积:", prod_per_student)
# 输出: 每位同学总成绩的连乘积: [  723360  663000  756480]

#计算累积积
cumprod_value = np.cumprod(arr, axis=1)
print("按行的累积积:\n", cumprod_value)
# 输出:
# [[  85   7820  687360]
#  [  78   7020  596700]
#  [  90   7830  720360]]

# 计算中位数
median_value = np.median(arr)
print("中位数:", median_value)
# 输出: 中位数: 88.0

# 计算每门课的中位数
median_per_subject = np.median(arr, axis=0)
print("每门课的中位数:", median_per_subject)
# 输出: 每门课的中位数: [85. 90. 88.]

# 计算标准差
std_value = np.std(arr)
print("标准差:", std_value)
# 输出: 标准差: 4.47

# 计算每门课的标准差
std_per_subject = np.std(arr, axis=0)
print("每门课的标准差:", std_per_subject)
# 输出: 每门课的标准差: [6.06 1.53 3.06]

矩阵的结果保存
#

矩阵规模可能很大,所以有时需要将计算结果保存到文件中。Numpy 提供了多种方法来保存和加载数组数据。 这里展示了进行两个数组的加法运算,然后保存结果到文本文件中:

import numpy as np

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

# 进行数组加法运算
result_array = array1 + array2

# 打印结果,查看加法运算后的数组
print("加法运算结果:\n", result_array)

# 使用np.savetxt保存结果到文本文件
# 假设我们要将结果保存到名为'result.txt'的文件中
np.savetxt("result.txt", result_array, fmt="%d", delimiter=",", newline="\n")

# 注意:fmt参数指定了每个元素的格式,这里是整数'%d'
# delimiter参数指定了元素之间的分隔符,这里是逗号','
# newline参数指定了每行结束后的换行符,这里是换行符'\n'
# 这些参数可以根据需要进行调整

在上述例子中,我们首先创建了两个一维数组 array1array2,并进行了加法运算,得到了结果数组 result_array。然后,我们使用 np.savetxt 函数将 result_array 保存到名为 result.txt 的文本文件中。在 np.savetxt 函数中,我们通过 fmt 参数指定了每个元素的格式(这里是整数格式’%d’),通过 delimiter 参数指定了元素之间的分隔符(这里是逗号,),以及通过newline参数指定了每行结束后的换行符(这里是换行符\n)。

np.savetxt很灵活,支持多种参数定制输出文件格式,除了上述参数,还有一些常用的:

  • header:可以添加文件的头部信息,通常用于描述数据的含义。
  • footer:可以添加文件的尾部信息。
  • comments:用于指定头部和尾部信息的注释符号,默认为#,如果headerfooter参数中包含该字符,将被视为注释。

例如,我们可以向文件中添加标题和注释:

np.savetxt('result_with_header.txt', result_array, fmt='%d', delimiter=',',
           header='这是加法运算的结果', comments='# ')

这样,生成的文件将包含标题信息,并且每行前面会有注释符号#

如要要从文件中加载数组,可以使用numpy.loadtxt()函数:

# 使用loadtxt加载之前保存的数组
# 根据保存时指定的格式和分隔符来加载
loaded_arr = np.loadtxt('array_data.txt', delimiter=',', dtype=float)

# 如果你保存时没有指定fmt或者delimiter,这里也需要相应地调整
# 比如,如果默认分隔符是空格,且没有指定fmt
# loaded_arr = np.loadtxt('array_data_no_fmt.txt', dtype=float)

# 打印加载的数组来验证
print(loaded_arr)

The End.

【科学计算】NumPy及其基本使用 - 这篇文章属于一个选集。
§ 3: 本文

相关文章

1.1 Numpy数组基础
·4883 字·10 分钟· loading
科学计算 Study Python
使用 LLaMAFactory 实现大模型微调
·7000 字·14 分钟· loading
Study LLM
【书评】紫禁城的黄昏
·2110 字·5 分钟· loading
书评
【功能更新】添加时间线和影音记录页面
·344 字·1 分钟· loading
功能更新
2025 七月集
·62 字·1 分钟· loading
步履不停 25#胶片 日常
书影音记录
·12 字·1 分钟· loading