学习numpy详解

发布于:2025-08-11 ⋅ 阅读:(13) ⋅ 点赞:(0)

NumPy:Python 科学计算的基石与实战指南

在数据科学、机器学习和科学计算的世界里,NumPy(Numerical Python)是一个不可或缺的基础库。自 2005 年首次发布以来,它已经成为 Python 生态系统中处理数值计算的核心工具,为 Pandas、Matplotlib、Scikit-learn 等众多知名库提供了底层支持。本文将深入剖析 NumPy 的核心原理、关键功能和最佳实践,通过丰富的代码案例帮助你掌握这一强大工具,提升数据处理效率。

一、为什么需要 NumPy?

Python 作为一种简洁易用的编程语言,在数据分析领域备受青睐,但它的原生数据结构在处理大规模数值计算时存在明显短板。让我们通过一个简单对比理解 NumPy 的价值。

1.1 原生列表的局限性

Python 列表(list)虽然灵活,但在数值计算场景下存在三大问题:

  • 存储效率低:列表可以存储不同类型的元素,这导致额外的类型信息存储开销
  • 计算效率差:对列表进行元素级运算需要显式循环,操作繁琐且速度缓慢
  • 缺乏数学操作:没有内置的矩阵运算、广播机制等数值计算必需的功能

python

运行

# 原生列表的元素级运算需要循环
a = [1, 2, 3, 4, 5]
b = [6, 7, 8, 9, 10]

# 元素级加法
result = []
for x, y in zip(a, b):
    result.append(x + y)
print(result)  # [7, 9, 11, 13, 15]

1.2 NumPy 的优势

NumPy 通过引入同构多维数组(ndarray) 解决了这些问题,带来了四大核心优势:

  • 内存高效:同构数组(所有元素类型相同)减少了类型信息存储,内存占用通常比列表少 50% 以上
  • 矢量化操作:无需循环即可对整个数组进行运算,代码更简洁
  • 计算加速:底层使用 C 语言实现核心运算,速度比纯 Python 快 10-100 倍
  • 丰富的数学函数:内置了大量线性代数、统计分析等数值计算函数

python

运行

import numpy as np

# NumPy的元素级运算(矢量化)
a = np.array([1, 2, 3, 4, 5])
b = np.array([6, 7, 8, 9, 10])
print(a + b)  # [ 7  9 11 13 15]

1.3 性能对比实验

让我们通过一个实验直观感受 NumPy 的性能优势。计算两个包含 100 万个元素的数组的元素级乘积:

python

运行

import time

# 创建大规模数据
size = 1_000_000
python_list1 = list(range(size))
python_list2 = list(range(size, 2*size))
numpy_arr1 = np.arange(size)
numpy_arr2 = np.arange(size, 2*size)

# 测试Python列表(需要循环)
start = time.time()
result_list = [x * y for x, y in zip(python_list1, python_list2)]
list_time = time.time() - start

# 测试NumPy数组(矢量化操作)
start = time.time()
result_numpy = numpy_arr1 * numpy_arr2
numpy_time = time.time() - start

print(f"Python列表耗时: {list_time:.4f}秒")
print(f"NumPy数组耗时: {numpy_time:.4f}秒")
print(f"NumPy速度提升: {list_time/numpy_time:.1f}倍")

在我的测试环境中,结果显示 NumPy 的速度提升了约 50 倍(实际结果可能因硬件而异)。这种性能差距在处理更大规模数据时会更加明显。

二、NumPy 核心:ndarray 数据结构

ndarray(N-dimensional array,N 维数组)是 NumPy 的核心数据结构,理解它的特性是掌握 NumPy 的基础。

2.1 ndarray 的基本属性

每个 ndarray 都有几个关键属性,描述了数组的结构和内容:

  • ndim:数组的维度(秩)
  • shape:数组在每个维度上的大小,以元组表示
  • size:数组中元素的总数
  • dtype:数组中元素的数据类型
  • itemsize:每个元素的字节大小
  • nbytes:数组占用的总字节数

python

运行

import numpy as np

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

print("数组内容:")
print(arr)
print("\n基本属性:")
print(f"维度 (ndim): {arr.ndim}")
print(f"形状 (shape): {arr.shape}")
print(f"元素总数 (size): {arr.size}")
print(f"数据类型 (dtype): {arr.dtype}")
print(f"每个元素字节数 (itemsize): {arr.itemsize}")
print(f"总字节数 (nbytes): {arr.nbytes}")

输出结果:

plaintext

数组内容:
[[1 2 3]
 [4 5 6]
 [7 8 9]]

基本属性:
维度 (ndim): 2
形状 (shape): (3, 3)
元素总数 (size): 9
数据类型 (dtype): int64
每个元素字节数 (itemsize): 8
总字节数 (nbytes): 72

2.2 数据类型(dtype)

NumPy 支持比 Python 更多样的数据类型,合理选择可以节省内存并提高效率:

类型 描述
int8/16/32/64 有符号整数,分别占用 1/2/4/8 字节
uint8/16/32/64 无符号整数
float16/32/64 浮点数,分别对应半精度 / 单精度 / 双精度
complex64/128 复数,分别由两个 32 位 / 64 位浮点数组成
bool 布尔值(True/False)
object Python 对象类型
string_ 固定长度字符串
unicode_ 固定长度 Unicode 字符串

创建数组时可以指定数据类型:

python

运行

# 指定数据类型
arr_int32 = np.array([1, 2, 3], dtype=np.int32)
arr_float = np.array([1.5, 2.5, 3.5], dtype=np.float64)
arr_bool = np.array([True, False, True], dtype=np.bool_)

print(f"int32数组: {arr_int32.dtype}")
print(f"float64数组: {arr_float.dtype}")
print(f"bool数组: {arr_bool.dtype}")

# 类型转换
arr_converted = arr_int32.astype(np.float32)
print(f"转换后的类型: {arr_converted.dtype}")

选择合适的数据类型至关重要。例如,在处理图像数据时,通常使用uint8(0-255 范围)存储像素值,比使用默认的int64节省 8 倍内存。

2.3 创建 ndarray 的常用方法

NumPy 提供了多种创建数组的方法,适用于不同场景:

2.3.1 从 Python 列表创建

最基本的方法是使用np.array()从 Python 列表或元组创建:

python

运行

# 1维数组
arr1d = np.array([1, 2, 3, 4, 5])

# 2维数组(列表的列表)
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 3维数组
arr3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])

print(f"1维数组形状: {arr1d.shape}")  # (5,)
print(f"2维数组形状: {arr2d.shape}")  # (3, 3)
print(f"3维数组形状: {arr3d.shape}")  # (2, 2, 2)
2.3.2 创建特殊数组

NumPy 提供了创建常见特殊数组的函数:

python

运行

# 全零数组
zeros = np.zeros((3, 4), dtype=np.int32)

# 全一数组
ones = np.ones((2, 3), dtype=np.float64)

# 填充特定值的数组
full = np.full((2, 2), 7)  # 填充7

# 单位矩阵(对角线为1,其余为0的方阵)
eye = np.eye(4)

# 对角矩阵(对角线为指定值,其余为0)
diag = np.diag([1, 2, 3, 4])

print("全零数组:")
print(zeros)
print("\n全一数组:")
print(ones)
print("\n单位矩阵:")
print(eye)
2.3.3 序列数组

创建具有规律序列的数组:

python

运行

# 类似range,但返回数组
arange = np.arange(10)  # [0 1 2 3 4 5 6 7 8 9]
arange_step = np.arange(2, 20, 3)  # 从2开始,步长3,到20为止:[ 2  5  8 11 14 17]

# 生成等间隔的浮点数
linspace = np.linspace(0, 1, 5)  # 0到1之间5个等间隔点:[0.   0.25 0.5  0.75 1.  ]

# 生成对数间隔的数组
logspace = np.logspace(0, 3, 4)  # 10^0到10^3之间4个点:[   1.   10.  100. 1000.]

print("arange结果:", arange)
print("linspace结果:", linspace)

arangelinspace的区别在于:arange指定步长,linspace指定元素数量,后者更适合需要均匀分布的场景(如绘图的坐标轴)。

2.3.4 随机数组

NumPy 的random模块提供了生成各种随机数组的功能,在模拟和机器学习中非常实用:

python

运行

# 设置随机种子,保证结果可复现
np.random.seed(42)

# 均匀分布随机数([0,1)区间)
rand = np.random.rand(2, 3)  # 2行3列

# 标准正态分布随机数(均值0,标准差1)
randn = np.random.randn(3, 2)

# [low, high)区间的随机整数
randint = np.random.randint(1, 10, size=(2, 2))  # 1到10之间的整数,2x2数组

# 从给定数组中随机选择
choice = np.random.choice([1, 2, 3, 4, 5], size=3)  # 从列表中选3个元素

# 随机排列
permutation = np.random.permutation(10)  # 0-9的随机排列

print("均匀分布随机数组:")
print(rand)
print("\n随机整数数组:")
print(randint)

三、ndarray 的索引与切片

高效访问数组元素是数据处理的基础操作。NumPy 提供了灵活多样的索引和切片方式,支持对任意维度的数组进行操作。

3.1 基本索引

与 Python 列表类似,ndarray 支持通过整数索引访问单个元素,不同维度用逗号分隔:

python

运行

import numpy as np

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

# 访问单个元素(第2行第3列,注意索引从0开始)
print(arr[1, 2])  # 7

# 也可以使用多个方括号(不推荐)
print(arr[1][2])  # 7,与上面等价但效率更低

# 3维数组索引
arr3d = np.array([[[1, 2], [3, 4]], 
                  [[5, 6], [7, 8]]])
print(arr3d[0, 1, 0])  # 3(第1个块,第2行,第1列)

3.2 切片操作

切片用于获取数组的子数组,格式为start:stop:step(步长默认为 1):

python

运行

# 1维数组切片
arr1d = np.arange(10)
print(arr1d[2:7])    # [2 3 4 5 6](从索引2到6,不包含7)
print(arr1d[:5])     # [0 1 2 3 4](从开头到4)
print(arr1d[5:])     # [5 6 7 8 9](从5到结尾)
print(arr1d[::2])    # [0 2 4 6 8](步长为2)
print(arr1d[::-1])   # [9 8 7 6 5 4 3 2 1 0](反转数组)

# 2维数组切片(行切片, 列切片)
arr2d = np.array([[1, 2, 3, 4],
                  [5, 6, 7, 8],
                  [9, 10, 11, 12]])

# 获取前2行,前3列
print(arr2d[:2, :3])
# [[1 2 3]
#  [5 6 7]]

# 获取所有行,第2列以后
print(arr2d[:, 2:])
# [[ 3  4]
#  [ 7  8]
#  [11 12]]

# 获取第1行和第3行,第2列和第4列
print(arr2d[[0, 2], [1, 3]])  # [ 2 12]

3.3 布尔索引

布尔索引允许根据条件筛选元素,返回满足条件的元素组成的 1 维数组:

python

运行

# 创建示例数组
data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
names = np.array(['Bob', 'Joe', 'Bob'])

# 创建布尔数组
mask = names == 'Bob'
print("布尔掩码:", mask)  # [ True False  True]

# 使用布尔数组索引
print("Bob对应的数据行:\n", data[mask])
# [[1 2 3]
#  [7 8 9]]

# 复合条件(&表示与,|表示或,注意用括号)
mask_complex = (names == 'Bob') & (data[:, 0] > 3)
print("满足复合条件的数据行:\n", data[mask_complex])  # [[7 8 9]]

# 修改满足条件的元素
data[data < 5] = 0
print("修改后的数据:\n", data)
# [[0 0 0]
#  [0 5 6]
#  [7 8 9]]

布尔索引在数据清洗中非常有用,例如过滤异常值、选择特定条件的样本等。

3.4 花式索引

花式索引(Fancy Indexing)使用整数数组作为索引,允许按任意顺序选取元素:

python

运行

# 1维数组花式索引
arr1d = np.arange(10, 20)
indices = [1, 3, 5, 7]
print(arr1d[indices])  # [11 13 15 17]

# 2维数组花式索引
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
row_indices = [0, 2, 3]  # 选择第0、2、3行
col_indices = [1, 2, 0]  # 选择第1、2、0列

# 选择(0,1), (2,2), (3,0)位置的元素
print(arr2d[row_indices, col_indices])  # [ 2  9 10]

# 结合切片和花式索引
print(arr2d[row_indices, 1:])  # 选择指定行的第1列及以后
# [[ 2  3]
#  [ 8  9]
#  [11 12]]

花式索引返回的是原数组的副本而非视图,修改结果不会影响原数组,这与切片操作不同。

四、数组形状操作

在数据处理中,经常需要调整数组的形状以适应不同的算法要求。NumPy 提供了多种灵活的形状操作方法。

4.1 重塑数组(reshape)

reshape方法可以在不改变数据的情况下调整数组的形状,新形状的元素总数必须与原数组相同:

python

运行

import numpy as np

# 创建1维数组
arr = np.arange(12)
print("原数组:", arr)  # [ 0  1  2  3  4  5  6  7  8  9 10 11]

# 重塑为3x4的2维数组
reshaped = arr.reshape(3, 4)
print("3x4数组:\n", reshaped)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# 重塑为2x2x3的3维数组
reshaped3d = arr.reshape(2, 2, 3)
print("2x2x3数组:\n", reshaped3d)
# [[[ 0  1  2]
#   [ 3  4  5]]
#  [[ 6  7  8]
#   [ 9 10 11]]]

# 可以指定-1让NumPy自动计算维度大小
auto_shape = arr.reshape(2, -1)  # 2行,自动计算列数
print("自动计算列数:", auto_shape.shape)  # (2, 6)

4.2 转置(transpose)与轴交换

转置是矩阵运算中的常见操作,T属性或transpose方法可以实现:

python

运行

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

# 转置(行变列,列变行)
transposed = arr.T
print("转置数组:\n", transposed)
# [[1 4]
#  [2 5]
#  [3 6]]

# 3维数组转置(需要指定轴的顺序)
arr3d = np.arange(24).reshape(2, 3, 4)
print("3维数组形状:", arr3d.shape)  # (2, 3, 4)

# 交换轴0和轴1
transposed3d = arr3d.transpose(1, 0, 2)
print("交换轴后的形状:", transposed3d.shape)  # (3, 2, 4)

# 也可以使用swapaxes交换两个轴
swapped = arr3d.swapaxes(0, 2)
print("交换轴0和2后的形状:", swapped.shape)  # (4, 3, 2)

转置在矩阵乘法、图像处理(如通道转换)等场景中非常有用。

4.3 数组拼接与拆分

4.3.1 拼接数组

NumPy 提供了concatenatevstack(垂直拼接)和hstack(水平拼接)等函数用于组合数组:

python

运行

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

# 垂直拼接(按行拼接,增加行数)
v_concat = np.vstack((arr1, arr2))
print("垂直拼接:\n", v_concat)
# [[1 2]
#  [3 4]
#  [5 6]
#  [7 8]]

# 水平拼接(按列拼接,增加列数)
h_concat = np.hstack((arr1, arr2))
print("水平拼接:\n", h_concat)
# [[1 2 5 6]
#  [3 4 7 8]]

# 使用concatenate指定轴(0=垂直,1=水平)
concat_axis0 = np.concatenate([arr1, arr2], axis=0)
concat_axis1 = np.concatenate([arr1, arr2], axis=1)

# 3维数组拼接
arr3d1 = np.arange(12).reshape(2, 2, 3)
arr3d2 = np.arange(12, 24).reshape(2, 2, 3)
concat_3d = np.concatenate([arr3d1, arr3d2], axis=1)
print("3维数组拼接后的形状:", concat_3d.shape)  # (2, 4, 3)
4.3.2 拆分数组

splitvsplithsplit函数用于将数组拆分为多个子数组:

python

运行

# 创建一个6x4的数组
arr = np.arange(24).reshape(6, 4)
print("原数组:\n", arr)

# 垂直拆分(按行拆分)为3个2x4的数组
vsplit_result = np.vsplit(arr, 3)
print("垂直拆分后的第一个子数组:\n", vsplit_result[0])

# 水平拆分(按列拆分)为2个6x2的数组
hsplit_result = np.hsplit(arr, 2)
print("水平拆分后的第一个子数组:\n", hsplit_result[0])

# 使用split指定轴和拆分位置
split_result = np.split(arr, [2, 4], axis=0)  # 在第2行和第4行处拆分
print("按指定位置拆分后的数组数量:", len(split_result))  # 3

4.4 展平数组

展平数组将多维数组转换为 1 维数组,常用方法有ravelflatten

python

运行

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

# ravel返回视图(修改会影响原数组)
raveled = arr.ravel()
raveled[0] = 100
print("raveled修改后原数组:\n", arr)  # 原数组的第一个元素已改变

# flatten返回副本(修改不影响原数组)
flattened = arr.flatten()
flattened[1] = 200
print("flattened修改后原数组:\n", arr)  # 原数组未改变

# 使用reshape(-1)也可以展平数组
flattened_reshape = arr.reshape(-1)
print("reshape展平结果:", flattened_reshape)

ravelflatten的主要区别在于返回的是视图还是副本:ravel更高效(不复制数据),但修改结果可能影响原数组;flatten更安全,但会复制数据,内存开销更大。

五、矢量化操作与广播机制

矢量化(Vectorization)和广播(Broadcasting)是 NumPy 高效的核心秘密,掌握它们可以写出简洁高效的代码。

5.1 矢量化操作

矢量化允许对整个数组进行操作而无需编写循环,NumPy 内部会将操作转换为高效的 C 语言循环:

python

运行

import numpy as np

# 创建两个大型数组
arr1 = np.random.rand(1_000_000)
arr2 = np.random.rand(1_000_000)

# 矢量化加法(无需循环)
result_vectorized = arr1 + arr2

# 等效的循环实现(效率极低,仅作示例)
result_loop = np.empty_like(arr1)
for i in range(len(arr1)):
    result_loop[i] = arr1[i] + arr2[i]

# 验证结果一致
print(np.array_equal(result_vectorized, result_loop))  # True

NumPy 提供了丰富的矢量化函数(称为通用函数,ufunc):

python

运行

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

# 算术运算
print("加法:", arr + 2)        # [3 4 5 6 7]
print("乘法:", arr * 3)        # [ 3  6  9 12 15]
print("平方:", arr **2)       # [ 1  4  9 16 25]

# 三角函数
print("正弦值:", np.sin(arr))  # [ 0.8415  0.9093  0.1411 -0.7568 -0.9589]

# 指数和对数
print("指数:", np.exp(arr))    # [  2.718   7.389  20.086  54.598 148.413]
print("自然对数:", np.log(arr)) # [0.    0.693 1.098 1.386 1.609]

# 比较运算(返回布尔数组)
print("大于2:", arr > 2)       # [False False  True  True  True]

5.2 广播机制

广播是 NumPy 处理不同形状数组之间算术运算的规则集,它可以让较小的数组 "广播" 到较大数组的形状,从而进行元素级运算。

广播的基本规则:

  1. 如果两个数组的维度数量不同,维度较少的数组会在前面补 1,直到维度数量相同
  2. 如果两个数组在某个维度上的大小不同,且其中一个为 1,则该维度会被扩展以匹配另一个数组的大小
  3. 如果两个数组在某个维度上的大小都不为 1 且不相等,则无法广播,会抛出错误

python

运行

# 标量与数组的广播
scalar = 5
arr = np.array([1, 2, 3])
print(scalar + arr)  # [6 7 8]

# 不同形状数组的广播
arr1 = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]])  # 形状(3,3)
arr2 = np.array([1, 2, 3])                          # 形状(3,)

# 广播后arr2的形状变为(1,3),再扩展为(3,3)
print(arr1 + arr2)
# [[1 2 3]
#  [2 3 4]
#  [3 4 5]]

# 另一个广播示例
arr3 = np.array([[1], [2], [3]])  # 形状(3,1)
arr4 = np.array([1, 2, 3])        # 形状(3,)

# arr3广播为(3,3),arr4广播为(1,3)再扩展为(3,3)
print(arr3 + arr4)
# [[2 3 4]
#  [3 4 5]
#  [4 5 6]]

广播可以大幅减少内存使用,避免创建不必要的副本。例如,将一个形状为 (1000, 1000) 的数组与一个形状为 (1000,) 的数组相加,广播机制会自动处理形状匹配,而无需显式复制。

但需注意,不当使用广播可能导致意外结果,建议在处理高维数组时明确检查形状是否兼容:

python

运行

# 检查广播兼容性
def check_broadcastable(shape1, shape2):
    """检查两个形状是否可以广播"""
    try:
        np.broadcast_shapes(shape1, shape2)
        return True
    except ValueError:
        return False

print(check_broadcastable((3, 4), (4,)))      # True
print(check_broadcastable((3, 4), (3,)))      # False(第0维不匹配)
print(check_broadcastable((2, 1, 3), (5, 1))) # True,广播后为(2,5,3)

六、NumPy 的数学与统计函数

NumPy 提供了丰富的数学和统计函数,涵盖了数据处理中常用的各种运算。

6.1 聚合函数

聚合函数对数组进行统计摘要,返回单个值或沿指定轴的统计结果:

python

运行

import numpy as np

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

# 求和
print("总和:", arr.sum())               # 45
print("按行求和:", arr.sum(axis=1))     # [ 6 15 24]
print("按列求和:", arr.sum(axis=0))     # [12 15 18]

# 均值
print("平均值:", arr.mean())            # 5.0
print("按行均值:", arr.mean(axis=1))    # [2. 5. 8.]

# 其他常用聚合函数
print("最大值:", arr.max())             # 9
print("最小值:", arr.min())             # 1
print("标准差:", arr.std())             # 2.7386127875258306
print("方差:", arr.var())               # 7.5
print("累计和:", arr.cumsum())          # [ 1  3  6 10 15 21 28 36 45]
print("累计积:", arr.cumprod())         # [   1    2    6   24  120  720  5040 40320 362880]

可以通过keepdims参数保持聚合后的维度:

python

运行

# 保持维度
sum_keepdims = arr.sum(axis=1, keepdims=True)
print("保持维度的行求和形状:", sum_keepdims.shape)  # (3, 1)
print("保持维度的行求和:\n", sum_keepdims)
# [[ 6]
#  [15]
#  [24]]

6.2 矩阵运算

NumPy 提供了完整的线性代数运算支持,通过np.linalg模块实现:

python

运行

# 创建两个矩阵
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

# 矩阵乘法(使用@运算符或np.dot)
print("矩阵乘法:\n", A @ B)
# [[19 22]
#  [43 50]]
print("等价于np.dot:\n", np.dot(A, B))  # 与上面结果相同

# 元素级乘法
print("元素级乘法:\n", A * B)
# [[ 5 12]
#  [21 32]]

# 矩阵转置
print("A的转置:\n", A.T)
# [[1 3]
#  [2 4]]

# 矩阵的逆(需方阵且可逆)
A_inv = np.linalg.inv(A)
print("A的逆矩阵:\n", A_inv)
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# 验证A与A_inv的乘积是单位矩阵
print("A * A_inv:\n", A @ A_inv)
# [[1.0000000e+00 1.1102230e-16]
#  [0.0000000e+00 1.0000000e+00]](近似单位矩阵)

# 行列式
print("A的行列式:", np.linalg.det(A))  # -2.0

# 求解线性方程组Ax = b
b = np.array([[5], [11]])
x = np.linalg.solve(A, b)
print("方程组的解:\n", x)  # [[1.], [2.]]

6.3 其他常用数学函数

python

运行

# 绝对值
arr = np.array([-1, -2, 3, -4])
print("绝对值:", np.abs(arr))  # [1 2 3 4]

# 取整函数
arr = np.array([1.2, 2.5, 3.7, 4.1])
print("向下取整:", np.floor(arr))  # [1. 2. 3. 4.]
print("向上取整:", np.ceil(arr))   # [2. 3. 4. 5.]
print("四舍五入:", np.round(arr))  # [1. 2. 4. 4.]

# clip函数(限制值的范围)
arr = np.array([1, 2, 3, 4, 5])
print("限制在2-4之间:", np.clip(arr, 2, 4))  # [2 2 3 4 4]

# 排序
arr = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print("排序结果:", np.sort(arr))  # [1 1 2 3 4 5 6 9]

# 唯一值
arr = np.array([1, 2, 2, 3, 3, 3, 4])
print("唯一值:", np.unique(arr))  # [1 2 3 4]

七、实战案例:NumPy 在数据处理中的应用

7.1 数据标准化与归一化

在机器学习中,特征标准化是重要的预处理步骤,NumPy 可以高效完成:

python

运行

import numpy as np

# 生成示例数据(100个样本,5个特征)
np.random.seed(42)
data = np.random.randn(100, 5) * 10 + 5  # 均值为5,标准差为10

# 方法1:手动标准化(均值为0,标准差为1)
mean = data.mean(axis=0)
std = data.std(axis=0)
standardized_manual = (data - mean) / std

# 方法2:使用广播机制简化代码
standardized = (data - data.mean(axis=0)) / data.std(axis=0)

# 验证标准化结果(均值接近0,标准差接近1)
print("标准化后的均值:", standardized.mean(axis=0).round(4))
print("标准化后的标准差:", standardized.std(axis=0).round(4))

# 数据归一化(缩放到[0,1]区间)
min_val = data.min(axis=0)
max_val = data.max(axis=0)
normalized = (data - min_val) / (max_val - min_val)

print("\n归一化后的最小值:", normalized.min(axis=0).round(4))
print("归一化后的最大值:", normalized.max(axis=0).round(4))

7.2 图像像素操作

图像在计算机中存储为三维数组(高度 × 宽度 × 通道),NumPy 可以方便地进行像素级操作:

python

运行

import numpy as np
import matplotlib.pyplot as plt

# 创建一个64x64的RGB测试图像
np.random.seed(42)
image = np.random.randint(0, 256, size=(64, 64, 3), dtype=np.uint8)

# 1. 提取红色通道
red_channel = image[:, :, 0].copy()

# 2. 将图像转为灰度图(使用 luminance 公式)
# 灰度 = 0.299*R + 0.587*G + 0.114*B
gray_image = 0.299 * image[:, :, 0] + 0.587 * image[:, :, 1] + 0.114 * image[:, :, 2]
gray_image = gray_image.astype(np.uint8)  # 转换为整数类型

# 3. 图像反转(负片效果)
inverted_image = 255 - image

# 4. 图像裁剪(提取中心32x32区域)
cropped_image = image[16:48, 16:48, :]

# 显示结果
plt.figure(figsize=(12, 8))

plt.subplot(2, 3, 1)
plt.imshow(image)
plt.title("原始图像")

plt.subplot(2, 3, 2)
plt.imshow(red_channel, cmap='gray')
plt.title("红色通道")

plt.subplot(2, 3, 3)
plt.imshow(gray_image, cmap='gray')
plt.title("灰度图")

plt.subplot(2, 3, 4)
plt.imshow(inverted_image)
plt.title("负片效果")

plt.subplot(2, 3, 5)
plt.imshow(cropped_image)
plt.title("裁剪后的图像")

plt.tight_layout()
plt.show()

7.3 数值积分与微分

NumPy 结合 SciPy 可以进行科学计算,例如数值积分和微分:

python

运行

import numpy as np
import matplotlib.pyplot as plt

# 定义函数 f(x) = sin(x) + x/5
def f(x):
    return np.sin(x) + x / 5

# 生成x值
x = np.linspace(0, 10, 1000)
y = f(x)

# 数值积分(使用梯形法则)
integral = np.trapz(y, x)
print(f"函数在[0,10]区间的积分值: {integral:.4f}")

# 数值微分(使用中心差分)
dx = x[1] - x[0]
dy = np.gradient(y, dx)  # 计算导数

# 绘制函数及其导数
plt.figure(figsize=(10, 6))
plt.plot(x, y, label='f(x) = sin(x) + x/5')
plt.plot(x, dy, label="f'(x)(导数)")
plt.fill_between(x, y, alpha=0.3, color='blue')
plt.grid(True, linestyle='--', alpha=0.7)
plt.axhline(y=0, color='k', linestyle='-', alpha=0.3)
plt.axvline(x=0, color='k', linestyle='-', alpha=0.3)
plt.title('函数及其导数(数值计算)')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.show()

八、性能优化与最佳实践

8.1 避免 Python 循环

NumPy 的最大优势在于矢量化操作,应尽量避免在 Python 中显式循环:

python

运行

import numpy as np
import time

# 创建大型数组
size = 1_000_000
arr = np.random.rand(size)

# 低效:使用Python循环
start = time.time()
result_loop = np.empty_like(arr)
for i in range(size):
    result_loop[i] = np.sin(arr[i]) * np.cos(arr[i])
loop_time = time.time() - start

# 高效:使用矢量化操作
start = time.time()
result_vectorized = np.sin(arr) * np.cos(arr)
vec_time = time.time() - start

print(f"循环耗时: {loop_time:.4f}秒")
print(f"矢量化耗时: {vec_time:.4f}秒")
print(f"加速比: {loop_time / vec_time:.1f}倍")

8.2 利用视图而非副本

尽量使用数组切片(返回视图)而非复制数据,减少内存占用:

python

运行

# 创建数组
arr = np.arange(10)

# 切片返回视图(不复制数据)
view = arr[2:7]
view[0] = 100  # 修改视图会影响原数组
print("原数组:", arr)  # [  0   1 100   3   4   5   6   7   8   9]

# 使用copy()创建副本
copy = arr[2:7].copy()
copy[0] = 200  # 修改副本不影响原数组
print("原数组:", arr)  # [  0   1 100   3   4   5   6   7   8   9]

8.3 合理选择数据类型

根据需求选择合适的数据类型,减少内存使用:

python

运行

# 不同数据类型的内存占用对比
arr_int64 = np.array([1, 2, 3], dtype=np.int64)
arr_int32 = np.array([1, 2, 3], dtype=np.int32)
arr_float64 = np.array([1.0, 2.0, 3.0], dtype=np.float64)
arr_float32 = np.array([1.0, 2.0, 3.0], dtype=np.float32)

print(f"int64 内存: {arr_int64.nbytes} 字节")  # 24
print(f"int32 内存: {arr_int32.nbytes} 字节")  # 12(节省50%)
print(f"float64 内存: {arr_float64.nbytes} 字节")  # 24
print(f"float32 内存: {arr_float32.nbytes} 字节")  # 12(节省50%)

8.4 使用 inplace 操作

对于大型数组,使用 inplace 操作(带_后缀的方法)可以避免创建副本:

python

运行

# 创建大型数组
arr = np.random.rand(1_000_000)

# 普通操作(创建副本)
start = time.time()
arr_squared = arr **2
copy_time = time.time() - start

# inplace操作(不创建副本)
start = time.time()
arr_squared_inplace = np.empty_like(arr)
np.square(arr, out=arr_squared_inplace)  # 使用out参数指定输出
inplace_time = time.time() - start

print(f"普通操作耗时: {copy_time:.6f}秒")
print(f"inplace操作耗时: {inplace_time:.6f}秒")

8.5 结合 Numba 加速

对于无法避免循环的场景,可以使用 Numba 库将 Python 函数编译为机器码:

python

运行

# 需要安装numba:pip install numba
from numba import jit
import numpy as np
import time

# 使用Numba编译函数
@jit(nopython=True)  # nopython模式提供最佳性能
def numba_accelerated(arr):
    result = np.empty_like(arr)
    for i in range(arr.shape[0]):
        result[i] = np.sin(arr[i]) * np.cos(arr[i])
    return result

# 创建测试数据
arr = np.random.rand(1_000_000)

# 第一次运行包含编译时间
start = time.time()
numba_accelerated(arr)
compile_time = time.time() - start

# 第二次运行(已编译)
start = time.time()
result_numba = numba_accelerated(arr)
run_time = time.time() - start

print(f"编译时间: {compile_time:.4f}秒")
print(f"运行时间: {run_time:.4f}秒")

九、NumPy 生态系统与扩展

NumPy 是 Python 科学计算生态系统的基石,与许多其他库紧密集成:

-** Pandas :基于 NumPy 构建,提供更高层次的数据结构(DataFrame)和数据分析工具
-
 Matplotlib/Seaborn :利用 NumPy 数组进行数据可视化
-
 Scikit-learn :机器学习库,使用 NumPy 存储和处理数据
-
 TensorFlow/PyTorch :深度学习框架,底层使用类似 NumPy 的张量结构
-
 SciPy **:科学计算库,基于 NumPy 提供更专业的数学算法

学习 NumPy 不仅能直接提升数据处理能力,也是掌握这些高级库的基础。

十、总结与学习资源

NumPy 作为 Python 科学计算的基石,通过高效的 ndarray 数据结构和矢量化操作,解决了 Python 原生数据结构在数值计算中的效率问题。本文涵盖了 NumPy 的核心功能:

  1. ndarray 的基本概念和属性
  2. 数组的创建与操作
  3. 索引、切片和形状调整
  4. 矢量化操作与广播机制
  5. 数学与统计函数
  6. 实战应用与性能优化

掌握 NumPy 需要不断实践,以下推荐一些学习资源:

-** 官方文档 NumPy 官方文档 提供了最权威的参考
-
 书籍 :《Python for Data Analysis》(Wes McKinney 著)深入讲解了 NumPy 和 Pandas
-
 在线课程 :Coursera 上的 "Python for Everybody" 和 "Data Science Specialization" 包含 NumPy 内容
-
 练习平台 **:Kaggle 和 LeetCode 上有许多适合用 NumPy 解决的数据处理问题

NumPy 看似简单,但深入理解其内部机制(如视图与副本、广播规则)需要时间和实践。熟练掌握后,它将成为你数据科学工具箱中最锋利的武器之一,大幅提升数据处理效率和代码质量。

分享


网站公告

今日签到

点亮在社区的每一天
去签到