跳到主要内容

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.int88位有符号整数-128 到 127
np.int1616位有符号整数-32768 到 32767
np.int3232位有符号整数-2147483648 到 2147483647
np.int6464位有符号整数很大范围
np.uint88位无符号整数0 到 255
np.float1616位浮点数半精度
np.float3232位浮点数单精度
np.float6464位浮点数双精度(默认)
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 的基础概念:

  1. ndarray 对象:NumPy 的核心数据结构,支持多维数组
  2. 创建数组:从列表创建、使用内置函数创建
  3. 数据类型:多种数值类型和字符串类型
  4. 数组属性:ndim、shape、size、dtype 等
  5. 基本运算:算术运算、广播机制、矩阵乘法
  6. 比较和布尔索引:用于数据筛选

这些是 NumPy 最基础的概念,掌握它们是后续学习的基础。下一章我们将学习数组的创建方法。