第5节:Python编程基础 - NumPy数组操作

目录

1. NumPy简介

1.1 NumPy的核心优势

1.2 安装NumPy

2. NumPy数组基础

2.1 创建数组

从Python列表创建

使用内置函数创建特殊数组

2.2 数组属性

2.3 数据类型

3. 数组索引与切片

3.1 一维数组索引

3.2 多维数组索引

3.3 布尔索引

3.4 花式索引(Fancy Indexing)

4. 数组操作

4.1 形状操作

改变数组形状

转置数组

4.2 数组连接与分割

连接数组

分割数组

4.3 数组排序

5. 数组运算

5.1 基本数学运算

5.2 广播机制

5.3 矩阵运算

5.4 统计运算

6. 高级数组操作

6.1 通用函数(ufunc)

6.2 向量化函数

6.3 条件逻辑

6.4 集合操作

7. 文件输入输出

7.1 文本文件

7.2 二进制文件

8. 性能优化技巧

8.1 避免不必要的复制

8.2 使用向量化操作

8.3 使用NumPy内置函数

8.4 预分配数组

9. 实际应用示例

9.1 图像处理

9.2 数据分析

9.3 科学计算

10. 总结


1. NumPy简介

NumPy(Numerical Python)是Python科学计算的基础库,提供了一个强大的N维数组对象和一系列操作这些数组的函数。它是几乎所有高级数据分析工具(如Pandas、SciPy、Scikit-learn等)的基础。

1.1 NumPy的核心优势

  • 高性能数组计算:NumPy的数组操作在底层用C语言实现,比Python原生列表快得多

  • 丰富的数学函数:提供大量数学函数,支持向量化操作

  • 广播功能不同形状数组之间的运算机制

  • 线性代数运算:内置矩阵运算、傅里叶变换等高级功能

  • 内存效率:NumPy数组比Python列表占用更少内存

1.2 安装NumPy

使用pip安装NumPy:

pip install numpy

导入NumPy(通常使用np作为别名):

import numpy as np

2. NumPy数组基础

2.1 创建数组

NumPy的核心是ndarray(N-dimensional array,N维数组)对象。

从Python列表创建

# 一维数组
arr1 = np.array([1, 2, 3, 4, 5])
print(arr1)  # 输出: [1 2 3 4 5]

# 二维数组
arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2)
# 输出:
# [[1 2 3]
#  [4 5 6]]

使用内置函数创建特殊数组

# 创建全零数组
zeros = np.zeros(5)  # 一维5个0
zeros_2d = np.zeros((2, 3))  # 2行3列的全零矩阵

# 创建全1数组
ones = np.ones((3, 2))  # 3行2列的全1矩阵

# 创建单位矩阵
eye = np.eye(3)  # 3x3单位矩阵

# 创建未初始化的数组(内容为内存中的随机值)
empty = np.empty((2, 2))

# 创建等差数组
arange = np.arange(0, 10, 2)  # 从0开始到10(不含),步长为2
# 输出: [0 2 4 6 8]

# 创建等间隔数组
linspace = np.linspace(0, 1, 5)  # 0到1之间等间隔的5个数
# 输出: [0.   0.25 0.5  0.75 1.  ]

# 创建随机数组
random_arr = np.random.random((2, 2))  # 2x2的随机数数组,值在[0,1)之间

2.2 数组属性

NumPy数组有许多有用的属性:

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

print(arr.ndim)    # 数组维度:2
print(arr.shape)   # 数组形状:(2, 3)
print(arr.size)    # 数组元素总数:6
print(arr.dtype)   # 数组元素类型:int32或int64(取决于系统)
print(arr.itemsize) # 每个元素占用的字节数:4(对于int32)
print(arr.nbytes)  # 数组总字节数:24(6个元素×4字节)

2.3 数据类型

NumPy支持多种数据类型,可以在创建数组时指定:

# 指定数据类型
arr_float = np.array([1, 2, 3], dtype=np.float64)
arr_complex = np.array([1, 2, 3], dtype=np.complex128)

# 常见数据类型:
# np.int8, np.int16, np.int32, np.int64
# np.uint8, np.uint16, np.uint32, np.uint64
# np.float16, np.float32, np.float64
# np.complex64, np.complex128
# np.bool_
# np.object_ (Python对象)
# np.string_ (固定长度的字符串)
# np.unicode_ (固定长度的Unicode)

3. 数组索引与切片

3.1 一维数组索引

一维数组的索引与Python列表类似:

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

print(arr[3])     # 第4个元素:3
print(arr[-1])    # 最后一个元素:9
print(arr[2:5])   # 切片:[2, 3, 4]
print(arr[:5])    # 前5个元素:[0, 1, 2, 3, 4]
print(arr[5:])    # 从第6个开始:[5, 6, 7, 8, 9]
print(arr[::2])   # 步长为2:[0, 2, 4, 6, 8]
print(arr[::-1])  # 反转数组:[9, 8, 7, ..., 0]

3.2 多维数组索引

多维数组的索引使用逗号分隔的索引元组:

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

# 获取单个元素
print(arr[0, 1])  # 第1行第2列:2

# 获取行
print(arr[1])     # 第2行:[4, 5, 6]
print(arr[1, :])  # 同上

# 获取列
print(arr[:, 1])  # 第2列:[2, 5, 8]

# 子数组
print(arr[:2, 1:])  # 前2行,第2列及以后
# 输出:
# [[2 3]
#  [5 6]]

3.3 布尔索引

可以使用布尔数组作为索引:

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

# 创建布尔条件
mask = arr > 2
print(mask)  # [False False  True  True  True]

# 使用布尔数组索引
print(arr[mask])  # [3, 4, 5]
# 等价于
print(arr[arr > 2])

# 更复杂的条件
print(arr[(arr > 1) & (arr < 4)])  # [2, 3]
print(arr[(arr < 2) | (arr > 4)])  # [1, 5]

3.4 花式索引(Fancy Indexing)

使用整数数组进行索引:

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

# 使用整数列表索引
indices = [1, 3, 4]
print(arr[indices])  # [20, 40, 50]

# 二维数组示例
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 选择特定行
print(arr2d[[0, 2]])  # 第1行和第3行
# 输出:
# [[1 2 3]
#  [7 8 9]]

# 选择特定行和列
print(arr2d[[0, 2], [0, 1]])  # (0,0)和(2,1)位置的元素
# 输出: [1 8]

4. 数组操作

4.1 形状操作

改变数组形状

arr = np.arange(12)

# reshape方法不改变原数组,返回新数组
arr_2d = arr.reshape(3, 4)
print(arr_2d)
# 输出:
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# resize方法会改变原数组
arr.resize(4, 3)
print(arr)
# 输出:
# [[ 0  1  2]
#  [ 3  4  5]
#  [ 6  7  8]
#  [ 9 10 11]]

# 展平数组
flattened = arr_2d.flatten()  # 返回新数组
raveled = arr_2d.ravel()     # 返回视图(修改会影响原数组)

转置数组

arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr.T)
# 输出:
# [[1 4]
#  [2 5]
#  [3 6]]

4.2 数组连接与分割

连接数组

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

# 垂直堆叠(沿第一个轴)
print(np.vstack((a, b)))
# 输出:
# [[1 2]
#  [3 4]
#  [5 6]]

# 水平堆叠(沿第二个轴)
c = np.array([[7], [8]])
print(np.hstack((a, c)))
# 输出:
# [[1 2 7]
#  [3 4 8]]

# concatenate函数更通用
print(np.concatenate((a, b), axis=0))  # 等同于vstack
print(np.concatenate((a, c), axis=1))  # 等同于hstack

分割数组

arr = np.arange(12).reshape(3, 4)
print(arr)
# 输出:
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# 水平分割(沿列分割)
print(np.hsplit(arr, 2))  # 分成2个数组
# 输出:
# [array([[0, 1],
#        [4, 5],
#        [8, 9]]), 
#  array([[ 2,  3],
#        [ 6,  7],
#        [10, 11]])]

# 垂直分割(沿行分割)
print(np.vsplit(arr, 3))  # 分成3个数组
# 输出:
# [array([[0, 1, 2, 3]]), 
#  array([[4, 5, 6, 7]]), 
#  array([[ 8,  9, 10, 11]])]

# split函数更通用
print(np.split(arr, 2, axis=1))  # 等同于hsplit
print(np.split(arr, 3, axis=0))  # 等同于vsplit

4.3 数组排序

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

# 返回排序后的新数组
print(np.sort(arr))  # [1, 2, 3, 4, 5]

# 原数组不变
print(arr)  # [3, 1, 4, 2, 5]

# 原地排序
arr.sort()
print(arr)  # [1, 2, 3, 4, 5]

# 获取排序索引
arr = np.array([3, 1, 4, 2, 5])
indices = np.argsort(arr)
print(indices)  # [1, 3, 0, 2, 4]
print(arr[indices])  # [1, 2, 3, 4, 5]

# 多维数组排序
arr2d = np.array([[3, 1, 4], [2, 5, 0]])
print(np.sort(arr2d, axis=1))  # 每行排序
# 输出:
# [[1 3 4]
#  [0 2 5]]

print(np.sort(arr2d, axis=0))  # 每列排序
# 输出:
# [[2 1 0]
#  [3 5 4]]

5. 数组运算

5.1 基本数学运算

NumPy数组支持逐元素运算:

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

# 加法
print(a + b)  # [5, 7, 9]
print(np.add(a, b))  # 同上

# 减法
print(a - b)  # [-3, -3, -3]
print(np.subtract(a, b))  # 同上

# 乘法(逐元素相乘,不是矩阵乘法)
print(a * b)  # [4, 10, 18]
print(np.multiply(a, b))  # 同上

# 除法
print(b / a)  # [4., 2.5, 2.]
print(np.divide(b, a))  # 同上

# 幂运算
print(a ** 2)  # [1, 4, 9]
print(np.power(a, 2))  # 同上

# 取模
print(b % a)  # [0, 1, 0]
print(np.mod(b, a))  # 同上

5.2 广播机制

广播是NumPy对不同形状数组进行算术运算的方式:

# 标量与数组运算
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(arr + 10)
# 输出:
# [[11 12 13]
#  [14 15 16]]

# 不同形状数组运算
a = np.array([[1, 2, 3]])
b = np.array([[4], [5]])
print(a + b)
# 输出:
# [[5 6 7]
#  [6 7 8]]

广播规则:

  1. 如果两个数组的维度数不同,将形状较小的数组前面补1

  2. 如果两个数组在某个维度上的长度不同,且其中一个为1,则该维度扩展为另一个的长度

  3. 如果两个数组在任何维度上的长度都不相同且都不为1,则报错

5.3 矩阵运算

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

# 矩阵乘法
print(np.dot(a, b))
# 输出:
# [[19 22]
#  [43 50]]

# Python 3.5+可以使用@运算符
print(a @ b)  # 同上

# 点积(一维数组)
v = np.array([1, 2, 3])
w = np.array([4, 5, 6])
print(np.dot(v, w))  # 32 (1*4 + 2*5 + 3*6)

# 转置
print(a.T)
# 输出:
# [[1 3]
#  [2 4]]

# 逆矩阵
from numpy.linalg import inv
print(inv(a))
# 输出:
# [[-2.   1. ]
#  [ 1.5 -0.5]]

# 行列式
from numpy.linalg import det
print(det(a))  # -2.0

# 特征值和特征向量
from numpy.linalg import eig
eigenvalues, eigenvectors = eig(a)
print(eigenvalues)   # [-0.37228132  5.37228132]
print(eigenvectors)  # [[-0.82456484 -0.41597356]
                     #  [ 0.56576746 -0.90937671]]

5.4 统计运算

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

# 求和
print(np.sum(arr))       # 21 (所有元素和)
print(np.sum(arr, axis=0))  # [5, 7, 9] (每列的和)
print(np.sum(arr, axis=1))  # [6, 15] (每行的和)

# 平均值
print(np.mean(arr))      # 3.5
print(np.mean(arr, axis=0))  # [2.5, 3.5, 4.5]

# 标准差
print(np.std(arr))       # 1.707825127659933

# 方差
print(np.var(arr))       # 2.9166666666666665

# 最大值/最小值
print(np.max(arr))       # 6
print(np.min(arr))       # 1

# 最大值/最小值的索引
print(np.argmax(arr))    # 5 (扁平化后的索引)
print(np.argmin(arr, axis=0))  # [0, 0, 0] (每列最小值的行索引)

# 中位数
print(np.median(arr))    # 3.5

# 百分位数
print(np.percentile(arr, 50))  # 3.5 (等同于中位数)
print(np.percentile(arr, 25))  # 2.25 (第一四分位数)

# 累积和
print(np.cumsum(arr))    # [ 1  3  6 10 15 21]

6. 高级数组操作

6.1 通用函数(ufunc)

通用函数是对ndarray逐元素操作的函数:

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

# 三角函数
print(np.sin(arr))
# 输出: [0.0000000e+00 1.0000000e+00 1.2246468e-16]

# 指数和对数
print(np.exp(arr))
# 输出: [ 1.          4.81047738 23.14069263]

print(np.log(arr + 1))
# 输出: [0.         0.88137359 1.42138568]

# 绝对值
arr2 = np.array([-1, -2, 3])
print(np.abs(arr2))  # [1, 2, 3]

# 比较函数
a = np.array([1, 2, 3])
b = np.array([2, 2, 2])
print(np.maximum(a, b))  # [2, 2, 3]
print(np.minimum(a, b))  # [1, 2, 2]

6.2 向量化函数

将Python函数向量化,使其可以处理数组:

def my_func(x):
    return x ** 2 + 2 * x + 1

# 向量化函数
vec_func = np.vectorize(my_func)

arr = np.array([1, 2, 3, 4])
print(vec_func(arr))  # [ 4  9 16 25]

# 更高效的方法是直接使用数组运算
print(my_func(arr))  # 同上,更高效

6.3 条件逻辑

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

# 逐元素比较
print(x > y)  # [False False False  True  True]

# where函数
print(np.where(x > y, x, y))  # [5, 4, 3, 4, 5]

# 更复杂的条件
condition = (x > 2) & (y < 3)
print(condition)  # [False False  True  True False]
print(np.where(condition, x * y, x + y))  # [6, 6, 9, 8, 6]

6.4 集合操作

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

# 唯一值
print(np.unique(a))  # [1, 2, 3, 4]

# 交集
print(np.intersect1d(a, b))  # [3, 4]

# 并集
print(np.union1d(a, b))  # [1, 2, 3, 4, 5, 6]

# 差集(在a中但不在b中)
print(np.setdiff1d(a, b))  # [1, 2]

# 对称差集(仅在a或仅在b中)
print(np.setxor1d(a, b))  # [1, 2, 5, 6]

7. 文件输入输出

7.1 文本文件

# 保存数组到文本文件
arr = np.arange(10).reshape(2, 5)
np.savetxt('array.txt', arr, delimiter=',')

# 从文本文件加载数组
loaded_arr = np.loadtxt('array.txt', delimiter=',')
print(loaded_arr)

7.2 二进制文件

# 保存为.npy格式(单个数组)
np.save('array.npy', arr)

# 加载.npy文件
loaded_arr = np.load('array.npy')
print(loaded_arr)

# 保存多个数组为.npz格式
arr2 = np.array([1, 2, 3])
np.savez('arrays.npz', arr1=arr, arr2=arr2)

# 加载.npz文件
loaded = np.load('arrays.npz')
print(loaded['arr1'])
print(loaded['arr2'])

8. 性能优化技巧

8.1 避免不必要的复制

# 视图(不复制数据)
arr = np.arange(10)
view = arr[3:7]  # 创建视图
view[0] = 100    # 修改视图会影响原数组
print(arr)       # [0, 1, 2, 100, 4, 5, 6, 7, 8, 9]

# 复制数组
copy = arr[3:7].copy()
copy[0] = 0      # 修改副本不会影响原数组
print(arr)       # 不变

8.2 使用向量化操作

避免Python循环,使用NumPy内置函数:

# 不推荐:使用Python循环
arr = np.random.rand(1000000)
result = np.empty_like(arr)
for i in range(len(arr)):
    result[i] = arr[i] * 2

# 推荐:使用向量化操作
result = arr * 2  # 快得多

8.3 使用NumPy内置函数

# 不推荐:使用Python的sum
sum_result = sum(arr)  # 慢

# 推荐:使用NumPy的sum
sum_result = np.sum(arr)  # 快

8.4 预分配数组

# 不推荐:动态扩展数组
result = np.array([])
for i in range(100):
    result = np.append(result, i)  # 每次都会创建新数组

# 推荐:预分配数组
result = np.empty(100)
for i in range(100):
    result[i] = i

9. 实际应用示例

9.1 图像处理

NumPy数组可以表示图像:

from PIL import Image
import matplotlib.pyplot as plt

# 加载图像为NumPy数组
img = np.array(Image.open('example.jpg'))
print(img.shape)  # (height, width, channels)

# 转换为灰度图像
gray = np.mean(img, axis=2).astype(np.uint8)

# 调整亮度
brightened = np.clip(img * 1.5, 0, 255).astype(np.uint8)

# 显示图像
plt.imshow(brightened)
plt.show()

9.2 数据分析

# 模拟学生成绩数据
scores = np.random.randint(0, 100, size=(100, 5))  # 100名学生,5门课程

# 计算每门课程的平均分
mean_scores = np.mean(scores, axis=0)

# 找出最高分的学生
max_scores = np.max(scores, axis=1)
top_student = np.argmax(max_scores)

# 统计及格人数(>=60)
passed = np.sum(scores >= 60, axis=0)

print(f"各科平均分: {mean_scores}")
print(f"最高分学生索引: {top_student}")
print(f"各科及格人数: {passed}")

9.3 科学计算

解线性方程组:

# 解方程组:
# 3x + y = 9
# x + 2y = 8
A = np.array([[3, 1], [1, 2]])
b = np.array([9, 8])

# 使用numpy.linalg.solve
x = np.linalg.solve(A, b)
print(x)  # [2., 3.]

# 验证
print(np.allclose(np.dot(A, x), b))  # True

10. 总结

NumPy是Python科学计算的基础,提供了强大的多维数组对象和各种操作这些数组的函数。

本教程涵盖了:

  1. NumPy数组的创建和基本属性

  2. 数组索引和切片的各种方法

  3. 数组的形状操作和连接分割

  4. 基本的数学运算和统计运算

  5. 高级操作如广播、通用函数、向量化等

  6. 文件I/O和性能优化技巧

  7. 实际应用示例

掌握NumPy是学习Python数据分析和科学计算的重要一步。

通过实践这些操作,能够高效地处理数值数据,为更高级的数据分析、机器学习等任务打下坚实基础。