Numpy(python科学计算)

在数据分析、科学计算、机器学习等领域,NumPy 是一个不可或缺的 Python 库。它为 Python 提供了高效的多维数组对象以及大量的数学函数,极大地简化了数值计算的过程,提升了计算效率。本文将深入探讨 NumPy 的核心特性、基本操作以及实际应用场景,帮助你快速掌握这个强大的工具。

目录

一、NumPy 简介

二、NumPy 的安装与导入

三、NumPy 的核心:ndarray 对象

四、NumPy 数组的基本操作

(一)数组的索引与切片

(二)数组的形状操作

(三)数组的转置

(四)数组的拼接与分割

五、NumPy 数组的运算

(一)数组的基本运算

(二)广播机制

(三)数学函数应用

六、NumPy 的高级特性

(一)通用函数(ufunc)

(二)数组的迭代器

(三)随机数生成

七、NumPy 在实际应用中的案例

(一)数据分析中的数据预处理

(二)科学计算中的矩阵运算

(三)机器学习中的特征工程

八、NumPy 的性能优化技巧

九、NumPy 与其他库的集成

十、NumPy 的局限性与注意事项

一、NumPy 简介

NumPy,全称为 Numerical Python,是一个开源的 Python 科学计算库。它最初由 Travis Oliphant 在 2005 年创建,旨在解决 Python 在数值计算方面的效率问题。Python 本身虽然具有强大的可读性和易用性,但在处理大规模数值数据时,其内置的数据结构(如列表)效率较低。NumPy 的出现,弥补了这一缺陷,通过引入高效的多维数组对象 ndarray(N-dimensional array),使得 Python 在数值计算领域具备了与其他专业数学软件(如 MATLAB)相媲美的能力。

NumPy 的核心优势在于它的高效性和便捷性。高效性主要体现在以下几个方面:

  • 内存布局优化 :ndarray 对象在内存中以连续的块存储数据,这种连续的内存布局使得数据在内存中的访问更加高效,缓存命中率更高,从而大大提升了计算速度。

  • 向量化操作 :NumPy 支持向量化操作,即对整个数组进行操作,而不是像 Python 列表那样需要通过循环逐个元素操作。向量化操作可以充分利用现代 CPU 的指令集和并行计算能力,大大减少循环次数,提高计算效率。

  • 预编译的 C 代码 :NumPy 的底层核心算法是用 C 语言编写的,这使得 NumPy 在执行数值计算时具有接近 C 语言的速度,远超纯 Python 实现的代码。

便捷性则体现在 NumPy 提供了丰富的数组操作接口和大量的数学函数,使得数据处理和计算变得简单直观。例如,它可以轻松地进行数组的创建、索引、切片、形状变换、转置等操作,还提供了诸如求和、求积、统计分析、线性代数运算等数学函数,几乎涵盖了数值计算的各个方面。

二、NumPy 的安装与导入

要使用 NumPy,首先需要确保它已经安装在你的 Python 环境中。可以通过以下几种常见的安装方式进行安装:

  • 使用 pip 安装 :在命令行中输入 “pip install numpy”,pip 会自动下载并安装最新的 NumPy 版本及其依赖项。

  • 使用 conda 安装 :如果你使用 Anaconda 发行版,可以在命令行中输入 “conda install numpy”,conda 会从其默认的仓库中安装适合你环境的 NumPy 版本。

安装完成后,在 Python 脚本或交互式环境中,通过以下代码导入 NumPy:

import numpy as np

通常我们会使用 “np” 作为 NumPy 的别名,这样在后续的代码中可以更简洁地调用 NumPy 的函数和类。

三、NumPy 的核心:ndarray 对象

ndarray 是 NumPy 的核心数据结构,它是一个多维数组对象,用于存储同类型元素的集合。数组中的元素可以通过索引访问,索引从 0 开始。ndarray 对象具有以下几个重要的属性:

  • ndim :表示数组的维度数,即数组的轴数。例如,一个一维数组的 ndim 为 1,一个二维数组的 ndim 为 2。

  • shape :表示数组在每个维度上的大小,即每个轴上的元素数量。例如,一个形状为(3,4)的二维数组,表示它有 3 行 4 列。

  • dtype :表示数组中元素的数据类型,如 int32、float64、bool 等。NumPy 提供了多种数据类型,可以满足不同的计算需求。

  • size :表示数组中元素的总个数,等于 shape 各维度大小的乘积。

  • itemsize :表示数组中每个元素所占的字节数,它与数据类型相关。例如,dtype 为 int32 的数组,itemsize 为 4 字节。

  • data :表示数组在内存中的实际数据缓冲区的起始地址,这是一个指向内存中实际数据的缓冲区对象。

创建 ndarray 对象有多种方法,常见的如下:

  • 从 Python 列表创建 :可以使用 numpy.array() 函数将 Python 列表转换为 ndarray。例如:

import numpy as np

# 创建一维数组
list_1d = [1, 2, 3, 4, 5]
array_1d = np.array(list_1d)
print(array_1d)
print("维度数:", array_1d.ndim)
print("形状:", array_1d.shape)

# 创建二维数组
list_2d = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
array_2d = np.array(list_2d)
print(array_2d)
print("维度数:", array_2d.ndim)
print("形状:", array_2d.shape)

运行结果:

[1 2 3 4 5]
维度数:1
形状:(5,)
[[1 2 3]
 [4 5 6]
 [7 8 9]]
维度数:2
形状:(3, 3)
  • 使用 NumPy 的内置函数创建 :NumPy 提供了一系列的函数用于创建特定形状和内容的数组,例如:

    • zeros(shape) :创建一个指定形状的数组,数组元素全部为 0。例如:

zeros_array = np.zeros((3, 4))
print(zeros_array)
[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]

* **ones(shape)** :创建一个指定形状的数组,数组元素全部为 1。例如:

ones_array = np.ones((2, 3))
print(ones_array)
[[1. 1. 1.]
 [1. 1. 1.]]

* **full(shape, fill_value)** :创建一个指定形状的数组,并用指定的值填充。例如:

full_array = np.full((2, 2), 7)
print(full_array)

[[7 7]
 [7 7]]

* **arange(start, stop, step)** :生成一个从 start 开始,到 stop 结束(不包括 stop),步长为 step 的一维数组。例如:

arange_array = np.arange(0, 10, 2)
print(arange_array)

输出结果:

[0 2 4 6 8]* **linspace(start, stop, num)** :生成一个从 start 到 stop(包括 start 和 stop)的 num 个等间距的数组。例如:

linspace_array = np.linspace(0, 1, 5)
print(linspace_array)

输出结果:

[0. 0.25 0.5 0.75 1. ]* **random.random(size)** :生成一个指定大小的数组,数组元素为 [0, 1) 区间的随机浮点数。例如:

random_array = np.random.random((2, 3))
print(random_array)

[[0.12345678 0.98765432 0.45678901]
 [0.23456789 0.67890123 0.89012345]]

* **eye(N)** :创建一个 N×N 的二维数组,主对角线元素为 1,其余元素为 0,即单位矩阵。例如:

eye_array = np.eye(3)
print(eye_array)

[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

* **empty(shape)** :创建一个指定形状的数组,数组元素未经初始化,其内容是随机的,取决于内存中的初始状态。例如:

empty_array = np.empty((2, 2))
print(empty_array)

[[0.61234568 0.23456789]
 [0.34567891 0.45678901]]

需要注意的是,empty() 函数创建的数组不会进行初始化操作,因此元素的值是不可预测的。通常只有在我们知道后续会立即对数组中的所有元素赋值时,才会使用 empty() 函数来创建数组,以节省初始化的时间和内存开销。

四、NumPy 数组的基本操作

掌握 NumPy 数组的基本操作是高效使用 NumPy 进行数值计算的基础,以下将详细介绍数组的索引、切片、形状操作、转置、拼接与分割等常用操作。

(一)数组的索引与切片

与 Python 列表类似,NumPy 数组也支持索引和切片操作,但由于 NumPy 数组是多维的,索引和切片的语法在高维数组中有所不同。

  • 一维数组的索引与切片

import numpy as np

array_1d = np.array([10, 20, 30, 40, 50])

# 索引访问元素
print(array_1d[0])   # 输出 10
print(array_1d[2])   # 输出 30

# 切片操作
print(array_1d[1:4])  # 输出 [20 30 40]
print(array_1d[:3])   # 输出 [10 20 30]
print(array_1d[3:])   # 输出 [40 50]
  • 二维数组的索引与切片

import numpy as np

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

# 索引访问元素
print(array_2d[0, 1])  # 输出 2,表示第一行第二列的元素
print(array_2d[2, 2])  # 输出 9,表示第三行第三列的元素

# 切片操作
# 提取子数组:从第 0 行到第 1 行(不包括第 2 行),第 0 列到第 2 列(不包括第 3 列)
sub_array = array_2d[0:2, 0:3]
print(sub_array)

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

也可以对某一行或某一列进行切片操作:

# 提取第一行的切片
row_slice = array_2d[0, 1:3]
print(row_slice)  # 输出 [2 3]

# 提取第二列的切片
column_slice = array_2d[1:3, 1]
print(column_slice)  # 输出 [5 8]
  • 多维数组的索引与切片 :对于更高维度的数组,索引和切片的操作遵循类似的规则,只需要在方括号中依次指定每个轴的索引或切片范围即可。例如,对于一个三维数组:

import numpy as np

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

# 索引访问元素:第一个三维块,第二行,第一列
print(array_3d[0, 1, 0])  # 输出 3

# 切片操作:提取第一个三维块的所有行和列
sub_array_3d = array_3d[0, :, :]
print(sub_array_3d)

[[1 2]
 [3 4]]

(二)数组的形状操作

NumPy 提供了一系列的方法来改变数组的形状,而不改变其数据内容。常见的形状操作包括:

  • reshape(shape) :返回一个新数组,其形状由 shape 参数指定,原数组的形状不变。例如:

import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
print("原数组形状:", array.shape)

reshaped_array = array.reshape(3, 2)
print("重塑后的数组形状:", reshaped_array.shape)
print(reshaped_array)

原数组形状: (2, 3)
重塑后的数组形状: (3, 2)
[[1 2]
 [3 4]
 [5 6]]

需要注意的是,新的形状中元素的总个数必须与原数组相同,否则会报错。例如,上面的例子中,原数组有 6 个元素,重塑为 3×2 的形状,元素总数仍为 6,符合要求。

  • resize(shape) :与 reshape 不同,resize 方法会直接修改原数组的形状,如果新的形状中元素个数与原数组不同,会截断或填充数组。例如:

import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
print("原数组:\n", array)

array.resize(3, 2)
print("调整大小后的数组:\n", array)

原数组:
 [[1 2 3]
 [4 5 6]]
调整大小后的数组:
 [[1 2]
 [3 4]
 [5 6]]

在这个例子中,原数组有 6 个元素,调整为 3×2 的形状后,元素仍然全部保留。如果调整为更小的形状,例如 2×2,则数组会被截断为前 4 个元素;如果调整为更大的形状,例如 3×3,则数组会被填充 0(如果是浮点数数组则填充 0.0)。

  • ravel() :将多维数组展平为一维数组,返回的是原数组的一个视图(即数据共享),对视图的修改会影响原数组。例如:

import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
flattened_array = array.ravel()
print(flattened_array)  # 输出 [1 2 3 4 5 6]

# 修改视图中的元素
flattened_array[0] = 10
print(array)  # 输出 [[10 2 3] [4 5 6]]

可以看到,修改展平后的数组的第一个元素,原二维数组的第一个元素也随之改变。

  • flatten() :同样将多维数组展平为一维数组,但它返回的是原数组的一个副本(即数据独立),对副本的修改不会影响原数组。例如:

import numpy as np

array = np.array([[1, 2, 3], [4, 5, 6]])
flattened_array = array.flatten()
print(flattened_array)  # 输出 [1 2 3 4 5 6]

# 修改副本中的元素
flattened_array[0] = 10
print(array)  # 输出 [[1 2 3] [4 5 6]]

在这里,修改副本的第一个元素,原数组保持不变。

(三)数组的转置

数组的转置是指将数组的行和列互换,对于二维数组来说,转置后的数组的形状是原数组形状的行数和列数互换。NumPy 提供了 transpose() 方法和 T 属性来实现数组的转置。

  • transpose() 方法

import numpy as np

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

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

transpose() 方法也可以接受一个参数 axes,用于指定轴的排列顺序。例如,对于一个三维数组,axes 参数可以用来指定新的轴的顺序。例如:

import numpy as np

array_3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
# 原数组的形状为 (2, 2, 2)
# 转置轴的顺序为 (1, 0, 2)
transposed_3d_array = array_3d.transpose((1, 0, 2))
print("原数组形状:", array_3d.shape)
print("转置后的数组形状:", transposed_3d_array.shape)

原数组形状: (2, 2, 2)
转置后的数组形状: (2, 2, 2)

在这个例子中,通过 transpose((1, 0, 2)),将原数组的轴 1 和轴 0 互换位置,轴 2 保持不变。

  • T 属性 :对于二维数组,可以直接使用 T 属性来获取其转置数组,这是一个更简洁的方式。例如:

import numpy as np

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

输出结果与使用 transpose() 方法相同:

复制

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

(四)数组的拼接与分割

在实际的数据处理过程中,常常需要将多个数组拼接成一个更大的数组,或者将一个大的数组分割成多个较小的数组。NumPy 提供了多种方法来实现这些操作。

  • 拼接数组

    • numpy.concatenate((a1, a2, ...), axis=0) :沿着指定的轴将多个数组拼接在一起。例如:

import numpy as np

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

# 沿行(轴 0)拼接
concatenated_array_row = np.concatenate((array1, array2), axis=0)
print(concatenated_array_row)

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

# 沿列(轴 1)拼接
concatenated_array_col = np.concatenate((array1, array2), axis=1)
print(concatenated_array_col)

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

* **numpy.vstack((a1, a2, ...))** :垂直堆叠数组,相当于沿行(轴 0)拼接,用于将数组堆叠成垂直的二维数组。例如:

import numpy as np

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

stacked_array = np.vstack((array1, array2))
print(stacked_array)

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

* **numpy.hstack((a1, a2, ...))** :水平堆叠数组,相当于沿列(轴 1)拼接,用于将数组堆叠成水平的二维数组。例如:

import numpy as np

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

stacked_array = np.hstack((array1, array2))
print(stacked_array)

[[1 4]
 [2 5]
 [3 6]]
  • 分割数组

    • numpy.split(ary, indices_or_sections, axis=0) :将数组沿着指定的轴分割成多个子数组。indices_or_sections 可以是一个整数,表示将数组均分为多少份;也可以是一个数组,表示分割的位置索引。例如:

import numpy as np

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

# 将数组均分为 3 份
split_arrays = np.split(array, 3)
print(split_arrays)

输出结果:

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

# 在索引 2 和 4 的位置分割数组
split_arrays_custom = np.split(array, [2, 4])
print(split_arrays_custom)

输出结果:

[array([1, 2]), array([3, 4]), array([5, 6])]* **numpy.vsplit(ary, indices_or_sections)** :垂直分割数组,相当于沿行(轴 0)分割二维数组。例如:

import numpy as np

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

# 将数组垂直均分为 2 份
split_arrays = np.vsplit(array, 2)
print(split_arrays)

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

* **numpy.hsplit(ary, indices_or_sections)** :水平分割数组,相当于沿列(轴 1)分割二维数组。例如:

import numpy as np

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

# 将数组水平均分为 2 份
split_arrays = np.hsplit(array, 2)
print(split_arrays)

输出结果:

复制

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

五、NumPy 数组的运算

NumPy 的强大之处在于其高效的数组运算能力,包括数组的基本运算、广播机制以及丰富的数学函数应用。

(一)数组的基本运算

NumPy 数组支持各种基本的数学运算,如加、减、乘、除等,这些运算通常是按元素进行的。例如:

import numpy as np

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

# 加法运算
add_result = array1 + array2
print(add_result)  # 输出 [ 6  8 10 12]

# 减法运算
sub_result = array1 - array2
print(sub_result)  # 输出 [-4 -4 -4 -4]

# 乘法运算
mul_result = array1 * array2
print(mul_result)  # 输出 [ 5 12 21 32]

# 除法运算
div_result = array1 / array2
print(div_result)  # 输出 [0.2        0.33333333 0.42857143 0.5       ]

对于二维数组,基本运算同样按元素进行,且要求两个数组的形状相同。例如:

import numpy as np

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

# 加法运算
add_result = array1 + array2
print(add_result)
# 输出 [[ 6  8]
#       [10 12]]

# 乘法运算
mul_result = array1 * array2
print(mul_result)
# 输出 [[ 5 12]
#       [21 32]]

此外,NumPy 还支持数组的标量运算,即数组与一个单独的数值进行运算。例如:

import numpy as np

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

# 加上标量 2
add_scalar_result = array + 2
print(add_scalar_result)  # 输出 [3 4 5 6]

# 乘以标量 3
mul_scalar_result = array * 3
print(mul_scalar_result)  # 输出 [ 3  6  9 12]

标量运算同样适用于二维或多维数组。

(二)广播机制

广播(Broadcasting)是 NumPy 中一个强大的机制,它允许在形状不同的数组之间进行运算,而无需显式地复制数组以匹配形状。广播规则如下:

  1. 如果两个数组的维度数不同,形状较小的数组会在前面补 1,直到维度数与较大的数组相同。例如,一个形状为 (3,) 的一维数组和一个形状为 (2, 3) 的二维数组进行运算时,一维数组的形状会被视为 (1, 3),然后在第一个轴上扩展为 2,以匹配二维数组的形状。

  2. 对于每个维度,如果两个数组在该维度的大小相等,或者其中一个数组在该维度的大小为 1,则认为它们是兼容的。如果两个数组在该维度的大小都大于 1 且不相等,则会引发错误。

  3. 广播后的数组形状是两个输入数组在每个维度上大小的最大值。

import numpy as np

array1 = np.array([[1, 2, 3], [4, 5, 6]])  # 形状 (2, 3)
array2 = np.array([10, 20, 30])            # 形状 (3,)

# 广播机制下进行加法运算
result = array1 + array2
print(result)

[[11 22 33]
 [44 55 66]]

在这里,array2 的形状为 (3,),在广播过程中,它会被视为形状为 (1, 3) 的数组,然后在第一个轴上扩展为 2,与 array1 的形状 (2, 3) 匹配,从而可以进行逐元素的加法运算。

广播机制极大地简化了数组运算的代码,提高了代码的可读性和效率。例如,如果我们想要将一个二维数组的每一列都加上不同的值,可以利用广播机制轻松实现,而无需对数组进行显式的转置或其他复杂操作。

(三)数学函数应用

NumPy 提供了大量的数学函数,用于执行各种数学运算,如三角函数、指数函数、对数函数、统计函数等。这些函数都可以直接应用于数组,并返回一个新的数组作为结果。

  • 三角函数

import numpy as np

array = np.array([0, np.pi/2, np.pi])

# 正弦函数
sin_result = np.sin(array)
print(sin_result)  # 输出 [0.00000000e+00 1.00000000e+00 1.22464680e-16]

# 余弦函数
cos_result = np.cos(array)
print(cos_result)  # 输出 [ 1.00000000e+00 6.12323400e-17 -1.00000000e+00]

# 正切函数
tan_result = np.tan(array)
print(tan_result)  # 输出 [0.00000000e+00 1.63312394e+16 -1.22464680e-16]
  • 指数与对数函数

import numpy as np

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

# 指数函数
exp_result = np.exp(array)
print(exp_result)  # 输出 [ 2.71828183  7.3890561  20.08553692 54.59815003]

# 自然对数函数
ln_result = np.log(array)
print(ln_result)  # 输出 [0.         0.69314718 1.09861229 1.38629436]

# 以 10 为底的对数函数
log10_result = np.log10(array)
print(log10_result)  # 输出 [0.         0.30103    0.47712125 0.60205999]
  • 统计函数

import numpy as np

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

# 求和
sum_result = np.sum(array)
print(sum_result)  # 输出 21

# 求平均值
mean_result = np.mean(array)
print(mean_result)  # 输出 3.5

# 求标准差
std_result = np.std(array)
print(std_result)  # 输出约 1.70782513

# 求最大值
max_result = np.max(array)
print(max_result)  # 输出 6

# 求最小值
min_result = np.min(array)
print(min_result)  # 输出 1

# 求中位数
median_result = np.median(array)
print(median_result)  # 输出 3.5

这些数学函数不仅可以应用于整个数组,还可以沿着指定的轴进行运算。例如,计算二维数组每行或每列的和:

import numpy as np

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

# 沿行(轴 1)求和
sum_along_row = np.sum(array, axis=1)
print(sum_along_row)  # 输出 [6 15]

# 沿列(轴 0)求和
sum_along_col = np.sum(array, axis=0)
print(sum_along_col)  # 输出 [5 7 9]

通过指定 axis 参数,可以方便地对数组的不同维度进行统计分析,这对于处理多维数据非常有用。

六、NumPy 的高级特性

除了上述的基本功能,NumPy 还具有一些高级特性,如通用函数(ufunc)、数组的迭代器、随机数生成等,这些特性为 NumPy 提供了更强大的功能和灵活性。

(一)通用函数(ufunc)

通用函数(ufunc)是 NumPy 提供的一组在数组上进行元素级操作的函数,它们是 NumPy 的核心功能之一。ufunc 函数具有以下特点:

  • 高效性 :ufunc 函数是用 C 语言实现的,因此它们的执行速度非常快,远超纯 Python 实现的循环操作。

  • 广播支持 :ufunc 函数内置了对广播机制的支持,可以自动处理形状不同的输入数组。

  • 矢量化操作 :ufunc 函数可以直接对数组进行操作,而无需编写显式的循环,使得代码更加简洁易读。

常见的 ufunc 函数包括前面提到的各种数学函数(如加、减、乘、除、三角函数、指数函数等),以及一些专门的函数,如:

  • np.add(x, y) :计算两个数组的加法。

  • np.subtract(x, y) :计算两个数组的减法。

  • np.multiply(x, y) :计算两个数组的乘法。

  • np.divide(x, y) :计算两个数组的除法。

  • np.power(x, y) :计算 x 的 y 次幂。

  • np.sqrt(x) :计算数组元素的平方根。

  • np.exp(x) :计算数组元素的指数。

  • np.log(x) :计算数组元素的自然对数。

使用 ufunc 函数的示例:

import numpy as np

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

# 使用 np.add 通用函数进行加法运算
add_result = np.add(array1, array2)
print(add_result)  # 输出 [ 6  8 10 12]

# 使用 np.power 通用函数计算平方
square_result = np.power(array1, 2)
print(square_result)  # 输出 [1 4 9 16]

# 使用 np.sqrt 通用函数计算平方根
sqrt_result = np.sqrt(array1)
print(sqrt_result)  # 输出 [1.         1.41421356 1.73205081 2.        ]

ufunc 函数还可以通过设置参数来控制运算的行为,例如:

  • out 参数 :指定输出结果存储的数组,这可以避免创建新的数组,从而节省内存和提高效率。例如:

import numpy as np

array1 = np.array([1, 2, 3, 4])
array2 = np.array([5, 6, 7, 8])
result_array = np.empty(4)

np.add(array1, array2, out=result_array)
print(result_array)  # 输出 [ 6.  8. 10. 12.]
  • where 参数 :指定一个布尔数组,只有在 where 参数为 True 的位置才执行运算,否则输出结果中的对应位置保持原值或为 0。例如:

import numpy as np

array1 = np.array([1, 2, 3, 4])
array2 = np.array([5, 6, 7, 8])
condition = np.array([True, False, True, False])

add_result = np.add(array1, array2, where=condition)
print(add_result)  # 输出 [ 6  2 10  4]

在这个例子中,只有在条件数组 condition 为 True 的位置(即索引 0 和 2),才执行加法运算,其他位置的结果保持 array1 中的原始值。

(二)数组的迭代器

NumPy 提供了专门的迭代器对象 numpy.nditer,用于高效地遍历数组中的元素。nditer 可以自动处理多维数组的迭代逻辑,使得我们可以更方便地对数组元素进行操作。

例如,使用 nditer 遍历一个二维数组:

import numpy as np

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

for element in np.nditer(array):
    print(element, end=' ')

输出结果:

1 2 3 4 5 6

nditer 默认以 C 风格的顺序(行优先)遍历数组元素。如果需要以 Fortran 风格的顺序(列优先)遍历,可以通过设置 order 参数为 'F':

import numpy as np

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

for element in np.nditer(array, order='F'):
    print(element, end=' ')

输出结果:

1 4 2 5 3 6

此外,nditer 还支持对数组元素的读写操作。通过设置 op_flags 参数,可以指定数组的访问模式。例如,将数组中的每个元素乘以 2:

import numpy as np

array = np.array([1, 2, 3, 4])
for element in np.nditer(array, op_flags=['readwrite']):
    element[...] = element * 2
print(array)  # 输出 [2 4 6 8]

在这里,op_flags 设置为 ['readwrite'],表示允许对数组元素进行读写操作,然后通过 element[...] = element * 2 来修改数组中的元素值。

(三)随机数生成

NumPy 提供了 numpy.random 模块,用于生成各种类型的随机数。这些随机数在模拟、统计采样、机器学习等领域有着广泛的应用。

  • 生成指定范围内的随机整数

import numpy as np

# 生成一个 1 到 10 之间的随机整数(不包括 10)
random_int = np.random.randint(1, 10)
print(random_int)

# 生成一个形状为 (3, 4) 的随机整数数组,元素范围在 0 到 100 之间
random_int_array = np.random.randint(0, 100, size=(3, 4))
print(random_int_array)
  • 生成 [0, 1) 区间的随机浮点数

import numpy as np

# 生成一个 [0, 1) 区间的随机浮点数
random_float = np.random.random()
print(random_float)

# 生成一个形状为 (2, 3) 的随机浮点数数组
random_float_array = np.random.random(size=(2, 3))
print(random_float_array)
  • 从标准正态分布中生成随机数

import numpy as np

# 生成一个标准正态分布的随机数
random_normal = np.random.randn()
print(random_normal)

# 生成一个形状为 (2, 2) 的标准正态分布随机数数组
random_normal_array = np.random.randn(2, 2)
print(random_normal_array)
  • 生成均匀分布的随机数

import numpy as np

# 生成一个在 [0, 1) 区间均匀分布的随机数
random_uniform = np.random.uniform(0, 1)
print(random_uniform)

# 生成一个形状为 (3,) 的在 [5, 10) 区间均匀分布的随机数数组
random_uniform_array = np.random.uniform(5, 10, size=3)
print(random_uniform_array)
  • 随机数种子 :为了能够重复产生相同的随机数序列,可以使用 numpy.random.seed() 函数设置随机数种子。例如:

import numpy as np

np.random.seed(42)
random_numbers1 = np.random.rand(3)
print(random_numbers1)

np.random.seed(42)
random_numbers2 = np.random.rand(3)
print(random_numbers2)

[0.37454012 0.95071431 0.73199394]
[0.37454012 0.95071431 0.73199394]

可以看到,设置相同的种子后,两次生成的随机数序列完全相同。这对于调试代码、复现实验结果等场景非常有用。

七、NumPy 在实际应用中的案例

为了更好地理解 NumPy 的实际应用价值,以下列举几个常见的应用场景及代码示例。

(一)数据分析中的数据预处理

在数据分析过程中,通常需要对原始数据进行预处理,如缺失值处理、数据标准化等。NumPy 提供了方便的工具来完成这些任务。

  • 处理缺失值

假设我们有一个包含缺失值的数据数组,缺失值用 NaN(Not a Number)表示。

import numpy as np

data = np.array([10.2, 12.5, np.nan, 14.8, np.nan, 17.3])

# 将缺失值替换为数组的均值
mean_value = np.nanmean(data)
cleaned_data = np.where(np.isnan(data), mean_value, data)
print(cleaned_data)

输出结果:

[10.2 12.5 15. 14.8 15. 17.3]

在这里,我们使用 np.nanmean() 函数计算数组中非 NaN 元素的均值,然后使用 np.where() 函数将 NaN 值替换为均值。

  • 数据标准化

数据标准化是将数据按比例缩放,使其落入一个小范围(如 0-1 或标准正态分布)的过程。以下是一个将数据标准化到 0-1 范围的示例:

import numpy as np

data = np.array([5.1, 7.8, 3.2, 9.5, 1.0])

# 计算数据的最大值和最小值
max_val = np.max(data)
min_val = np.min(data)

# 数据标准化到 [0, 1] 范围
scaled_data = (data - min_val) / (max_val - min_val)
print(scaled_data)

输出结果:

[0.4375 0.7625 0.25 1. 0. ]

通过简单的数学变换,我们将原始数据映射到了 0-1 范围内,便于后续的分析和建模。

(二)科学计算中的矩阵运算

在科学计算领域,矩阵运算是一个常见的任务。NumPy 提供了强大的矩阵运算功能,使得复杂的矩阵计算变得轻松简单。

  • 矩阵乘法

import numpy as np

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

# 使用 np.dot() 函数计算矩阵乘法
matrix_product = np.dot(matrix_a, matrix_b)
print(matrix_product)

[[19 22]
 [43 50]]

np.dot() 函数用于计算两个数组的点积,对于二维数组来说,就是矩阵乘法。

  • 求解线性方程组

假设我们有一个线性方程组:

复制

3x + y = 9
x + 2y = 8

可以使用 NumPy 的 np.linalg.solve() 函数来求解这个方程组:

import numpy as np

# 系数矩阵
A = np.array([[3, 1], [1, 2]])
# 常数项数组
B = np.array([9, 8])

# 求解线性方程组
solution = np.linalg.solve(A, B)
print(solution)

输出结果:

[2. 3.]

这表示方程组的解为 x=2,y=3。np.linalg.solve() 函数专门用于求解线性矩阵方程,它是 NumPy 线性代数模块(numpy.linalg)中的一个功能强大的工具。

(三)机器学习中的特征工程

在机器学习项目中,特征工程是至关重要的一环,NumPy 可以帮助我们高效地进行特征处理。

  • 特征缩放

特征缩放是将不同尺度的特征数据转换到同一尺度范围,以提高机器学习算法的性能。以下是一个使用 NumPy 进行特征缩放的示例:

import numpy as np

# 假设我们有两个特征:房屋面积(平方米)和房间数量
features = np.array([[150, 3], [200, 4], [180, 2], [220, 5]])

# 分别计算每个特征的均值和标准差
mean_area = np.mean(features[:, 0])
std_area = np.std(features[:, 0])
mean_rooms = np.mean(features[:, 1])
std_rooms = np.std(features[:, 1])

# 对特征进行标准化(Z-score 标准化)
scaled_features = np.zeros_like(features)
scaled_features[:, 0] = (features[:, 0] - mean_area) / std_area
scaled_features[:, 1] = (features[:, 1] - mean_rooms) / std_rooms

print(scaled_features)

[[ -0.59830287  -0.47140452]
 [  1.09660574   1.32453834]
 [ -0.29915143  -1.32453834]
 [  0.20084856   1.47140452]]

通过标准化处理,我们将房屋面积和房间数量这两个特征转换到了以均值为 0、标准差为 1 的标准正态分布范围内,这样可以使机器学习算法(如神经网络、支持向量机等)在训练过程中更快地收敛,提高模型的性能。

  • 特征选择

特征选择是从原始特征中选择出对模型最有用的特征子集,以减少特征维度、提高模型的泛化能力。以下是一个简单的特征选择示例,根据特征的方差来选择方差较大的特征:

import numpy as np

# 假设我们有三个特征
features = np.array([[2.5, 3.2, 0.8], [3.1, 4.5, 1.2], [1.8, 2.9, 0.5], [4.2, 5.1, 1.0]])

# 计算每个特征的方差
variances = np.var(features, axis=0)
print("特征方差:", variances)

# 选择方差大于 0.5 的特征
selected_features = features[:, variances > 0.5]
print("选择的特征:\n", selected_features)

特征方差: [1.09166667 1.32244444 0.08666667]
选择的特征:
 [[2.5 3.2]
 [3.1 4.5]
 [1.8 2.9]
 [4.2 5.1]]

在这里,我们计算了每个特征的方差,然后选择方差大于 0.5 的特征(即前两个特征),舍弃了方差较小的第三个特征,从而简化了特征空间,为后续的模型训练提供了更有意义的特征输入。

八、NumPy 的性能优化技巧

为了充分发挥 NumPy 的性能优势,在使用过程中掌握一些性能优化技巧是非常重要的。

  • 避免显式循环 :尽可能使用 NumPy 的内置函数和向量化操作,而不是使用 Python 的 for 循环或 while 循环对数组元素进行逐个操作。内置函数和向量化操作利用了 NumPy 的底层优化机制,能够更高效地利用 CPU 资源和内存带宽。例如,计算两个数组的点积时,使用 np.dot() 函数而不是手动编写循环计算:

import numpy as np

array1 = np.random.rand(1000)
array2 = np.random.rand(1000)

# 使用 np.dot() 函数计算点积(推荐)
dot_product = np.dot(array1, array2)

# 使用显式循环计算点积(不推荐)
dot_product_loop = 0.0
for i in range(len(array1)):
    dot_product_loop += array1[i] * array2[i]

print(dot_product == dot_product_loop)  # 在数值上应该相等(忽略浮点精度误差)

通常情况下,使用 np.dot() 函数的计算速度会比显式循环快很多,尤其是当数组较大时。

  • 预分配数组内存 :如果需要创建一个较大的数组,并且知道其最终的形状和大小,可以预先分配内存空间,而不是在循环中动态扩展数组。动态扩展数组会导致频繁的内存复制和重新分配,这会显著降低程序的性能。例如,预先分配一个数组来存储循环计算的结果:

import numpy as np

n = 10000
# 预分配数组内存
result = np.empty(n)

for i in range(n):
    # 进行一些计算并将结果存储到预分配的数组中
    result[i] = i ** 2 + 2 * i + 1

# 如果不预分配内存,而是动态扩展数组,性能会差很多
# result_dynamic = []
# for i in range(n):
#     result_dynamic.append(i ** 2 + 2 * i + 1)
# result_dynamic = np.array(result_dynamic)

预分配内存的方式可以大大减少内存操作的开销,从而提高代码的执行效率。

  • 使用合适的数据类型 :选择合适的数据类型可以减少内存占用,提高计算速度。例如,如果数据的取值范围较小,可以使用较小的数据类型(如 int8、float32)而不是较大的数据类型(如 int64、float64)。通过使用 np.dtype 来指定数组的数据类型,或者在创建数组时通过 dtype 参数来选择合适的数据类型:

import numpy as np

# 使用 int8 数据类型创建数组
array_int8 = np.array([1, 2, 3, 4, 5], dtype=np.int8)
print("int8 数组占用的内存字节数:", array_int8.nbytes)  # 输出 5

# 使用 int64 数据类型创建相同的数组
array_int64 = np.array([1, 2, 3, 4, 5], dtype=np.int64)
print("int64 数组占用的内存字节数:", array_int64.nbytes)  # 输出 40

在这个例子中,int8 数组占用的内存仅为 int64 数组的 1/8,在处理大规模数据时,这种内存节省是非常可观的,同时也可以加速内存访问和计算过程。

  • 利用广播机制 :合理利用广播机制可以避免创建不必要的中间数组,减少内存消耗和计算步骤。例如,将一个二维数组的每一列都加上一个不同的偏置值:

import numpy as np

data = np.random.rand(1000, 100)
biases = np.random.rand(100)

# 利用广播机制添加偏置(推荐)
data_with_bias = data + biases

# 不利用广播机制,手动复制偏置数组(不推荐)
# biases_replicated = np.tile(biases, (1000, 1))
# data_with_bias_no_broadcast = data + biases_replicated

在这里,第一种方法利用广播机制直接将形状为 (100,) 的偏置数组与形状为 (1000, 100) 的数据数组相加,得到正确的结果,而无需显式地将偏置数组复制为 (1000, 100) 的形状。这不仅节省了内存(避免创建 biases_replicated 数组),还减少了计算量,提高了代码的性能。

  • 使用 NumPy 的数学函数 :NumPy 提供的数学函数通常经过高度优化,能够利用 SIMD(单指令多数据)指令集等硬件特性来加速计算。在可能的情况下,尽量使用 NumPy 的内置数学函数,而不是自己编写类似的计算逻辑。例如,计算数组元素的平方根时,使用 np.sqrt() 函数而不是自己编写平方根计算代码:

import numpy as np

array = np.random.rand(1000000)

# 使用 NumPy 的 sqrt 函数(推荐)
sqrt_array = np.sqrt(array)

# 自己编写平方根计算逻辑(不推荐)
# sqrt_array_manual = array ** 0.5

虽然在这个例子中,自己编写 array ** 0.5 也能得到正确的结果,但 NumPy 的 np.sqrt() 函数在底层进行了更多的优化,执行速度通常会更快,尤其是在处理大规模数组时。

  • 使用视图(View)代替副本(Copy) :在某些操作中,如数组的切片操作,尽量使用视图(即原数组的一个引用)而不是创建副本,这样可以节省内存并提高程序的响应速度。只有在确实需要修改数据且不希望影响原数组的情况下,才考虑创建副本。例如:

import numpy as np

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

# 使用视图进行切片操作(推荐)
view_array = original_array[1:4]
print("视图数组:", view_array)

# 修改视图数组会影响原数组
view_array[0] = 10
print("修改后的原数组:", original_array)

# 如果需要副本,可以显式地创建(不推荐,除非必要)
# copy_array = original_array[1:4].copy()
# copy_array[0] = 20
# print("修改副本后的原数组:", original_array)  # 原数组不会改变

在上面的代码中,view_array 是 original_array 的一个视图,对 view_array 的修改会直接反映到 original_array 中。如果使用 copy() 方法创建副本,则副本与原数组的数据独立,修改副本不会影响原数组。在内存和性能敏感的应用场景中,合理选择视图或副本是非常重要的。

九、NumPy 与其他库的集成

NumPy 作为 Python 科学计算生态系统的基石,与其他众多库紧密集成,共同构成了强大的数据处理和分析工具链。

  • 与 Pandas 集成 :Pandas 是一个用于数据分析和处理的 Python 库,它在内部大量使用 NumPy 数组来存储数据。Pandas 的 DataFrame 和 Series 数据结构实际上是建立在 NumPy 的 ndarray 基础之上的。例如,我们可以将 NumPy 数组转换为 Pandas 的 DataFrame:

import numpy as np
import pandas as pd

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

# 将 NumPy 数组转换为 Pandas DataFrame
df = pd.DataFrame(array, columns=['A', 'B', 'C'])
print(df)
   A  B  C
0  1  2  3
1  4  5  6
2  7  8  9

同样,也可以将 Pandas 的 DataFrame 或 Series 转换回 NumPy 数组:

# 将 Pandas DataFrame 转换为 NumPy 数组
numpy_array = df.values
print(numpy_array)
[[1 2 3]
 [4 5 6]
 [7 8 9]]

这种无缝的转换使得我们可以充分利用 NumPy 的高效数组运算能力和 Pandas 的数据处理功能,实现复杂的数据分析任务。

  • 与 Matplotlib 集成 :Matplotlib 是一个广泛使用的 Python 绘图库,它与 NumPy 配合默契,可以方便地将 NumPy 数组中的数据可视化。例如,绘制一个简单的折线图:

import numpy as np
import matplotlib.pyplot as plt

# 创建 NumPy 数组作为数据
x = np.linspace(0, 10, 100)
y = np.sin(x)

# 使用 Matplotlib 绘制折线图
plt.plot(x, y)
plt.xlabel('x')
plt.ylabel('sin(x)')
plt.title('Sine Wave')
plt.show()

输出结果为一张显示正弦波的折线图。在这个例子中,我们使用 NumPy 的 linspace() 函数生成 x 值数组,使用 sin() 函数计算对应的 y 值数组,然后将这两个数组传递给 Matplotlib 的 plot() 函数进行绘图。通过这种方式,我们可以轻松地将数据计算和可视化结合起来,直观地展示数据的规律和趋势。

  • 与 SciPy 集成 :SciPy 是一个基于 NumPy 构建的科学计算库,它提供了更多高级的数学函数和算法,如信号处理、积分、优化、插值等。NumPy 作为 SciPy 的基础,为 SciPy 提供了高效的数组支持。例如,使用 SciPy 的 integrate 模块进行数值积分:

import numpy as np
from scipy import integrate

# 定义被积函数
def integrand(x):
    return np.exp(-x**2)

# 使用 SciPy 的 quad 函数计算定积分
result, error = integrate.quad(integrand, -np.inf, np.inf)
print("积分结果:", result)
print("估计误差:", error)

在这个例子中,我们使用 NumPy 的 exp() 函数定义了被积函数,然后调用 SciPy 的 quad() 函数计算该函数在整个实数范围上的定积分。NumPy 的高效数组运算能力与 SciPy 的专业数学算法相结合,使得我们可以轻松地处理复杂的科学计算问题。

  • 与 Scikit-learn 集成 :Scikit-learn 是一个流行的机器学习库,它基于 NumPy、SciPy 和 Matplotlib 构建。在 Scikit-learn 中,数据通常以 NumPy 数组的形式传递,这使得我们可以方便地将 NumPy 的数据处理功能与 Scikit-learn 的机器学习算法结合起来。例如,使用 Scikit-learn 的线性回归模型:

import numpy as np
from sklearn.linear_model import LinearRegression

# 生成训练数据
X = np.array([[1], [2], [3], [4], [5]])
y = np.array([2, 4, 6, 8, 10])

# 创建线性回归模型并拟合数据
model = LinearRegression()
model.fit(X, y)

# 预测新数据
X_new = np.array([[6], [7]])
y_pred = model.predict(X_new)
print("预测结果:", y_pred)

在这个例子中,我们使用 NumPy 数组存储训练数据和目标变量,然后将其传递给 Scikit-learn 的 LinearRegression 模型进行训练和预测。通过这种方式,我们可以充分利用 NumPy 的数据处理能力和 Scikit-learn 的机器学习算法,快速构建和评估各种机器学习模型。

十、NumPy 的局限性与注意事项

尽管 NumPy 在数值计算方面表现出色,但它也有一些局限性和需要注意的地方。

  • 内存消耗 :NumPy 数组在内存中存储为连续的块,虽然这提高了访问速度,但对于大规模数据来说,可能会消耗大量的内存。如果处理的数据量超过了系统内存的容量,可能会导致内存溢出错误或显著的性能下降。在这种情况下,可以考虑使用其他技术,如数据分块处理、利用磁盘存储(如使用 HDF5 文件格式)或使用分布式计算框架(如 Dask)来处理大规模数据。

  • 并行计算的局限性 :虽然 NumPy 的底层运算利用了多线程来加速计算(特别是对于支持多线程的 BLAS/LAPACK 库),但在某些情况下,它可能无法充分发挥多核 CPU 的并行计算能力。对于需要高度并行化的复杂计算任务,可能需要借助其他专门的并行计算库(如 multiprocessing、joblib 或 CUDA)来实现更高效的并行计算。

  • 数据类型转换的潜在问题 :在使用 NumPy 与其他 Python 库或函数交互时,需要注意数据类型转换的问题。NumPy 的数据类型与 Python 内置的类型之间存在差异,自动转换可能会导致意外的结果或性能损失。例如,将一个 NumPy 整数数组传递给一个期望 Python 整数列表的函数,可能会引发类型错误或计算结果不准确。因此,在进行数据交换时,需要显式地进行类型转换,以确保数据的正确性和一致性。

  • 与 Python 原生数据结构的兼容性 :虽然 NumPy 提供了高效的数组对象,但在与一些只支持 Python 原生数据结构(如列表、元组)的库或函数交互时,可能需要将 NumPy 数组转换为这些原生结构。这种转换会带来一定的性能开销,特别是在频繁转换大规模数据时。因此,在设计数据处理流程时,应尽量减少不必要的数据类型转换,或者选择能够直接支持 NumPy 数组的工具和库,以提高整体性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值