NumPy 基础语法
本章将介绍 NumPy 的核心概念,包括 ndarray 对象、数据类型、数组属性等基础知识。
ndarray 对象简介
ndarray(n-dimensional array)是 NumPy 的核心对象,它是一个多维数组容器。与 Python 原生的列表相比,ndarray 在存储效率和运算速度上都有显著优势。
为什么 NumPy 数组更快?
连续的内存存储:ndarray 在内存中是连续存储的,而 Python 列表存储的是对象的引用。这种存储方式使得数组元素可以快速访问。
固定的数据类型:ndarray 中的所有元素必须是相同的数据类型,这允许更高效的存储和计算。
优化的 C 实现:NumPy 的核心是用 C 语言编写的,执行效率远高于纯 Python 代码。
SIMD 指令支持:NumPy 可以利用 CPU 的 SIMD(单指令多数据)指令进行并行计算。
创建数组
从 Python 列表创建
最基本的方式是从 Python 列表创建数组:
import numpy as np
# 一维数组
a = np.array([1, 2, 3, 4, 5])
print(f"一维数组: {a}")
print(f"形状: {a.shape}")
# 二维数组
b = np.array([[1, 2, 3], [4, 5, 6]])
print(f"\n二维数组:\n{b}")
print(f"形状: {b.shape}")
# 指定数据类型
c = np.array([1, 2, 3], dtype=np.float32)
print(f"\nfloat32 数组: {c}")
print(f"数据类型: {c.dtype}")
使用内置函数创建
NumPy 提供了多种创建数组的便捷函数:
import numpy as np
# arange: 类似 range,生成等差数组
arr1 = np.arange(0, 10, 2) # 0, 2, 4, 6, 8
print(f"arange: {arr1}")
# linspace: 生成等间隔的数组
arr2 = np.linspace(0, 1, 5) # 0, 0.25, 0.5, 0.75, 1
print(f"linspace: {arr2}")
# zeros: 全零数组
arr3 = np.zeros((3, 4)) # 3x4 的全零矩阵
print(f"zeros:\n{arr3}")
# ones: 全一数组
arr4 = np.ones((2, 3)) # 2x3 的全一矩阵
print(f"ones:\n{arr4}")
# eye: 单位矩阵
arr5 = np.eye(3) # 3x3 单位矩阵
print(f"eye:\n{arr5}")
# full: 指定值的数组
arr6 = np.full((2, 3), 99) # 2x3 的全是 99 的矩阵
print(f"full:\n{arr6}")
# random: 随机数组
arr7 = np.random.rand(3, 2) # 3x2 的 [0, 1) 均匀分布随机数
print(f"random.rand:\n{arr7}")
arr8 = np.random.randn(3, 2) # 3x2 的标准正态分布随机数
print(f"random.randn:\n{arr8}")
arr9 = np.random.randint(0, 10, (3, 3)) # 3x3 的 [0, 10) 整数随机数
print(f"random.randint:\n{arr9}")
数据类型
NumPy 支持多种数据类型,常见的有:
| dtype | 说明 | 取值范围 |
|---|---|---|
np.int8 | 8位有符号整数 | -128 到 127 |
np.int16 | 16位有符号整数 | -32768 到 32767 |
np.int32 | 32位有符号整数 | -2147483648 到 2147483647 |
np.int64 | 64位有符号整数 | 很大范围 |
np.uint8 | 8位无符号整数 | 0 到 255 |
np.float16 | 16位浮点数 | 半精度 |
np.float32 | 32位浮点数 | 单精度 |
np.float64 | 64位浮点数 | 双精度(默认) |
np.bool_ | 布尔类型 | True / False |
np.complex64 | 复数(两个32位) | - |
np.complex128 | 复数(两个64位) | - |
数据类型指定与转换
import numpy as np
# 创建时指定类型
arr1 = np.array([1, 2, 3], dtype=np.float32)
print(f"指定类型: {arr1.dtype}")
# 类型转换
arr2 = np.array([1.5, 2.7, 3.9])
arr3 = arr2.astype(np.int32) # 转换为整数
print(f"转换前: {arr2}, 转换后: {arr3}")
# 查看数组类型
print(f"数组类型: {arr2.dtype}")
dtype 参数详解
创建数组时,dtype 参数可以接受多种形式的类型指定:
import numpy as np
# 使用字符串
arr1 = np.array([1, 2, 3], dtype='float32')
# 使用 NumPy 类型
arr2 = np.array([1, 2, 3], dtype=np.float64)
# 使用 Python 内置类型(会被映射到 NumPy 类型)
arr3 = np.array([1, 2, 3], dtype=float) # 等同于 float64
# 复数类型
arr4 = np.array([1+2j, 3+4j], dtype=np.complex128)
# 字符串类型
arr5 = np.array(['hello', 'world'], dtype='U10') # Unicode 字符串,最多10个字符
# 字节串类型
arr6 = np.array([b'hello', b'world'], dtype='S10') # 字节串,最多10字节
数组属性
ndarray 对象有多个重要属性,用于描述数组的特征:
import numpy as np
# 创建示例数组
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
print(f"数组:\n{arr}")
print(f"ndim(维度数): {arr.ndim}") # 2
print(f"shape(形状): {arr.shape}") # (2, 3)
print(f"size(元素总数): {arr.size}") # 6
print(f"dtype(数据类型): {arr.dtype}") # float32
print(f"itemsize(元素字节大小): {arr.itemsize}") # 4
print(f"nbytes(总字节数): {arr.nbytes}") # 24
print(f"flags(内存信息):\n{arr.flags}")
属性的实际意义
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6]])
# ndim: 数组的维度
print(f"一维数组维度: {np.array([1, 2, 3]).ndim}") # 1
print(f"二维数组维度: {arr.ndim}") # 2
print(f"三维数组维度: {np.array([[[1], [2]], [[3], [4]]]).ndim}") # 3
# shape: 每个维度的元素数量
print(f"shape: {arr.shape}") # (2, 3) 表示2行3列
# size: 所有元素的总数
print(f"size: {arr.size}") # 6
# 计算维度大小时
print(f"2x3x4 数组的大小: {np.zeros((2, 3, 4)).size}") # 24
数组的内存布局
行优先与列优先
NumPy 支持两种内存布局方式:
import numpy as np
# 默认是行优先(C 顺序)
arr_c = np.array([[1, 2, 3], [4, 5, 6]], order='C')
print(f"C 顺序:\n{arr_c}")
print(f"C 顺序 flatten: {arr_c.flatten()}")
# 列优先(Fortran 顺序)
arr_f = np.array([[1, 2, 3], [4, 5, 6]], order='F')
print(f"\nFortran 顺序:\n{arr_f}")
print(f"Fortran 顺序 flatten: {arr_f.flatten()}")
C 顺序(行优先):在内存中按行存储元素。二维数组的第一行元素在内存中是连续的。
Fortran 顺序(列优先):在内存中按列存储元素。二维数组的第一列元素在内存中是连续的。
连续性
import numpy as np
# 创建一个非连续的数组
arr = np.arange(12).reshape((3, 4))
print(f"是否 C 连续: {arr.flags['C_CONTIGUOUS']}")
print(f"是否 Fortran 连续: {arr.flags['F_CONTIGUOUS']}")
# 切片创建的数组通常是非连续的
sliced = arr[:, :2]
print(f"\n切片后是否 C 连续: {sliced.flags['C_CONTIGUOUS']}")
# 创建连续副本
contiguous = np.ascontiguousarray(sliced)
print(f"转换为 C 连续: {contiguous.flags['C_CONTIGUOUS']}")
基本运算
算术运算
NumPy 的算术运算是元素级的(element-wise):
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 基本运算
print(f"a + b = {a + b}") # [5, 7, 9]
print(f"a - b = {a - b}") # [-3, -3, -3]
print(f"a * b = {a * b}") # [4, 10, 18]
print(f"a / b = {a / b}") # [0.25, 0.4, 0.5]
print(f"a ** 2 = {a ** 2}") # [1, 4, 9]
广播机制
广播(Broadcasting)是 NumPy 处理不同形状数组的方式,使得不同形状的数组可以进行运算:
import numpy as np
# 标量与数组运算
arr = np.array([1, 2, 3])
print(f"arr + 10 = {arr + 10}") # [11, 12, 13]
# 一维与二维数组运算
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([1, 2, 3])
print(f"\n二维 + 一维:\n{a + b}")
# 结果:
# [[2, 4, 6],
# [5, 7, 9]]
# 行列向量
a = np.array([[1, 2, 3], [4, 5, 6]])
row = np.array([[1, 2, 3]]) # shape: (1, 3)
col = np.array([[1], [2]]) # shape: (2, 1)
print(f"\n行向量加:\n{a + row}")
print(f"\n列向量加:\n{a + col}")
矩阵乘法
import numpy as np
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
# 元素级乘法
print(f"元素级乘法:\n{a * b}")
# 矩阵乘法(Python 3.5+)
print(f"矩阵乘法 @:\n{a @ b}")
# 使用 dot 函数
print(f"矩阵乘法 dot:\n{np.dot(a, b)}")
# 一维数组的内积
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
print(f"向量内积: {np.dot(v1, v2)}") # 32 (1*4 + 2*5 + 3*6)
比较运算和布尔索引
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
# 比较运算返回布尔数组
print(f"arr > 3: {arr > 3}") # [False False False True True]
print(f"arr == 3: {arr == 3}") # [False False True False False]
# 布尔索引:选取满足条件的元素
print(f"arr[arr > 3]: {arr[arr > 3]}") # [4, 5]
# 复合条件
print(f"arr[(arr > 2) & (arr < 5)]: {arr[(arr > 2) & (arr < 5)]}") # [3, 4]
# np.where 实现条件选择
result = np.where(arr > 3, arr * 2, arr)
print(f"np.where: {result}") # [1, 2, 3, 8, 10]
小结
本章介绍了 NumPy 的基础概念:
- ndarray 对象:NumPy 的核心数据结构,支持多维数组
- 创建数组:从列表创建、使用内置函数创建
- 数据类型:多种数值类型和字符串类型
- 数组属性:ndim、shape、size、dtype 等
- 基本运算:算术运算、广播机制、矩阵乘法
- 比较和布尔索引:用于数据筛选
这些是 NumPy 最基础的概念,掌握它们是后续学习的基础。下一章我们将学习数组的创建方法。