NumPy数组创建与属性

高效数值计算的基础 — ndarray核心操作全面讲解

学习主题: NumPy数组创建方式与核心属性详解

核心内容: 从列表创建array()、特殊数组创建(zeros/ones/eye/arange/linspace)、随机数组生成、数组属性(ndim/shape/dtype/size/itemsize/nbytes)、数据类型体系(int/float/complex/bool)、astype类型转换、数组维度操作(reshape/ravel/flatten/transpose)

关键词: NumPy, ndarray, 数组创建, dtype, shape, reshape, 数据类型, Python科学计算

一、NumPy概述

NumPy(Numerical Python) 是Python科学计算生态中最为核心的基础库,提供了高性能的多维数组对象 ndarray 以及丰富的数组操作函数。几乎所有Python数据分析与科学计算的高级库(如Pandas、SciPy、scikit-learn、TensorFlow、PyTorch)都建立在NumPy的基础之上。

为什么学习NumPy数组?

  • 高性能: NumPy的底层使用C语言实现,向量化操作比纯Python循环快数十倍甚至上百倍
  • 内存效率: ndarray在内存中连续存储,访问速度快且内存占用远低于Python列表
  • 广播机制: 支持不同形状数组之间的算术运算,无需显式编写循环
  • 通用性强: 几乎所有Python数据科学生态工具都兼容NumPy数组

安装NumPy非常简单,只需在终端执行:

# 使用pip安装 pip install numpy # 使用conda安装 conda install numpy

安装完成后,通过以下方式导入:

import numpy as np # 验证安装版本 print(np.__version__) # 输出类似 1.26.3

二、创建NumPy数组

NumPy提供了多种创建数组的方式,覆盖了从基础数据转换到特殊数组生成的各类场景。

2.1 从Python列表创建:array()

np.array() 是最基础的数组创建函数,将Python列表或元组转换为ndarray对象。可以创建一维、二维甚至更高维度的数组。

# 一维数组 arr1d = np.array([1, 2, 3, 4, 5]) print(arr1d) # 输出: [1 2 3 4 5] # 二维数组(矩阵) arr2d = np.array([[1, 2, 3], [4, 5, 6]]) print(arr2d) # 输出: # [[1 2 3] # [4 5 6]] # 三维数组 arr3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) print(arr3d) # 输出: # [[[1 2] # [3 4]] # [[5 6] # [7 8]]] # 显式指定数据类型 arr_float = np.array([1, 2, 3], dtype=np.float64) print(arr_float.dtype) # float64

最佳实践

创建数组时,如果数据已经是列表形式,优先使用 np.array();如果需要生成特殊结构的数组,使用下面介绍的专用函数效率更高、代码更简洁。

2.2 特殊数组:zeros / ones / eye / full

这些函数用于创建全零数组、全一数组、单位矩阵以及指定填充值的数组,是初始化权重矩阵、占位数组时的常用工具。

# 全零数组 zeros_1d = np.zeros(5) print(zeros_1d) # [0. 0. 0. 0. 0.] zeros_2d = np.zeros((3, 4)) print(zeros_2d.shape) # (3, 4) # 全一数组 ones_2d = np.ones((2, 3)) print(ones_2d) # [[1. 1. 1.] # [1. 1. 1.]] # 单位矩阵 eye_mat = np.eye(4) print(eye_mat) # [[1. 0. 0. 0.] # [0. 1. 0. 0.] # [0. 0. 1. 0.] # [0. 0. 0. 1.]] # 指定填充值的数组 full_arr = np.full((3, 3), fill_value=7) print(full_arr) # [[7 7 7] # [7 7 7] # [7 7 7]]

2.3 序列生成:arange / linspace / logspace

这三个函数用于生成数值序列,arange 类似于Python内置的 range() 但支持浮点数步长;linspace 在指定区间内生成等间隔的固定数量数值;logspace 则按对数刻度生成序列。

# arange: 类似range(),支持浮点数 arr_a = np.arange(10) print(arr_a) # [0 1 2 3 4 5 6 7 8 9] arr_b = np.arange(2, 10, 2) print(arr_b) # [2 4 6 8] arr_c = np.arange(0, 1, 0.2) print(arr_c) # [0. 0.2 0.4 0.6 0.8] # linspace: 指定起始、结束和数量(包含结束点) arr_d = np.linspace(0, 1, 5) print(arr_d) # [0. 0.25 0.5 0.75 1. ] arr_e = np.linspace(0, 10, 4, endpoint=False) print(arr_e) # [0. 2.5 5. 7.5] # logspace: 按对数刻度生成 arr_f = np.logspace(0, 3, 4) print(arr_f) # [ 1. 10. 100. 1000.]

2.4 随机数组:random模块

NumPy的 random 模块提供了丰富的随机数生成函数,包括均匀分布、正态分布、随机整数、随机排列等,是模拟数据和机器学习初始化的重要工具。

# 设置随机种子(保证结果可复现) np.random.seed(42) # [0,1)均匀分布随机数 rand_arr = np.random.rand(3, 4) print(rand_arr) # 标准正态分布随机数 randn_arr = np.random.randn(2, 3) print(randn_arr) # 指定范围内的随机整数 rand_int = np.random.randint(0, 100, size=(3, 3)) print(rand_int) # 随机抽样(无放回) sample = np.random.choice(np.arange(10), size=5, replace=False) print(sample) # 例如 [1 7 4 9 3] # 随机排列 shuffled = np.random.permutation(10) print(shuffled) # 例如 [3 8 0 5 2 9 1 7 4 6] # 自定义分布:指定概率 probs = np.array([0.1, 0.2, 0.7]) weighted = np.random.choice(['A', 'B', 'C'], size=10, p=probs) print(weighted) # 多数为'C'

随机种子说明

np.random.seed() 用于设置随机数生成器的种子。使用相同的种子,每次生成的随机数序列完全相同,这在调试和复现实验结果时非常关键。在机器学习的实验中,固定随机种子是一项基本要求。

三、数组核心属性

ndarray对象拥有一组丰富的内置属性,可以快速获取数组的维度信息、形状、数据类型、元素个数、内存占用等关键元信息。熟练使用这些属性是高效操作NumPy数组的基础。

属性 含义 返回值示例
ndim 数组的维度数量(轴的数量) 2
shape 数组的形状,元组形式 (3, 4)
dtype 数组元素的数据类型 float64
size 数组中元素的总个数 12
itemsize 每个元素占用的字节数 8
nbytes 数组占用的总字节数 96
# 创建一个测试数组 arr = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], dtype=np.float64) print(f"数组维度: {arr.ndim}") # 2 print(f"数组形状: {arr.shape}") # (3, 4) print(f"数据类型: {arr.dtype}") # float64 print(f"元素总数: {arr.size}") # 12 print(f"每元素字节: {arr.itemsize}") # 8 print(f"总字节数: {arr.nbytes}") # 96
数组维度: 2 数组形状: (3, 4) 数据类型: float64 元素总数: 12 每元素字节: 8 总字节数: 96

3.1 ndim — 维度数量

ndim 返回数组的轴数(秩)。一维数组的 ndim=1,二维数组 ndim=2,以此类推。标量值(0维数组)的 ndim=0。

scalar = np.array(5) print(scalar.ndim) # 0(标量) vector = np.array([1, 2, 3]) print(vector.ndim) # 1(向量) matrix = np.array([[1, 2], [3, 4]]) print(matrix.ndim) # 2(矩阵) tensor = np.ones((2, 3, 4)) print(tensor.ndim) # 3(三维张量)

3.2 shape — 数组形状

shape 返回一个元组,表示数组在每个维度上的大小。这是NumPy中最常用的属性之一,理解 shape 对于数组操作至关重要。

arr = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]) print(arr.shape) # (2, 2, 2) # 含义:2个"页面",每个页面2行2列 # shape可以用于reshape(见后文) # 也常用于条件判断 if arr.shape[0] == arr.shape[1]: print("这是一个方阵")

3.3 dtype — 数据类型

dtype 描述了数组中每个元素的类型。NumPy的数据类型体系比Python原生类型更细粒度,支持从8位到128位的各类整数和浮点数。

dtype的重要性

  • 精确控制内存占用:例如 int32int64 节省一半内存
  • 确保计算精度:科学计算中 float64(双精度)是默认选择
  • 与外部系统互操作:读写二进制文件、与C/Fortran代码交互时需要匹配数据类型
  • 性能优化:使用更小精度的类型可提升缓存利用率和计算速度

3.4 size / itemsize / nbytes

size 返回数组中的总元素数量(等于 shape 中各维度值的乘积)。itemsize 返回每个元素的字节数,nbytessize * itemsize,表示数组占用的总内存字节数。

# 内存占用对比 arr_int32 = np.ones((1000, 1000), dtype=np.int32) arr_float64 = np.ones((1000, 1000), dtype=np.float64) print(f"int32 总字节: {arr_int32.nbytes}") # 4,000,000(约4MB) print(f"float64 总字节: {arr_float64.nbytes}") # 8,000,000(约8MB) print(f"int32 itemsize: {arr_int32.itemsize}") # 4 print(f"float64 itemsize: {arr_float64.itemsize}") # 8

四、数据类型详解

NumPy的数据类型体系非常丰富,比Python原生的 intfloat 更加精细化,提供了精确的位宽控制。

4.1 常用数据类型一览

类型 类型代码 描述 字节数
int8 'i1' 有符号8位整数 1
int32 'i4' 有符号32位整数 4
int64 'i8' 有符号64位整数 8
uint8 'u1' 无符号8位整数(0-255) 1
float16 'f2' 半精度浮点数 2
float32 'f4' 单精度浮点数 4
float64 'f8' 双精度浮点数(默认) 8
complex64 'c8' 单精度复数(实部+虚部各32位) 8
complex128 'c16' 双精度复数(实部+虚部各64位) 16
bool '?' 布尔类型(True/False) 1
# 不同类型数组的创建 int_arr = np.array([1, 2, 3], dtype=np.int32) print(int_arr.dtype) # int32 float_arr = np.array([1, 2, 3], dtype=np.float64) print(float_arr.dtype) # float64 complex_arr = np.array([1+2j, 3+4j], dtype=np.complex128) print(complex_arr.dtype) # complex128 bool_arr = np.array([True, False, True], dtype=np.bool_) print(bool_arr.dtype) # bool

4.2 astype() 类型转换

astype() 方法用于将数组从一种数据类型转换为另一种,返回一个新的数组(不会修改原数组)。类型转换在数据预处理、内存优化和精度控制中非常常用。

# 基础类型转换 arr_float = np.array([1.2, 2.7, 3.5, 4.9]) arr_int = arr_float.astype(np.int32) print(arr_int) # [1 2 3 4](向下取整截断) # 浮点数转布尔 arr_bool = arr_float.astype(np.bool_) print(arr_bool) # [ True True True True](非零为True) # 降低精度以节省内存 arr_high = np.random.randn(1000, 1000).astype(np.float32) print(f"float32内存: {arr_high.nbytes / 1024 / 1024:.2f} MB") # 相比float64节省一半内存 # 类型代码简写 arr_code = np.array([1, 2, 3]).astype('f4') print(arr_code.dtype) # float32 # 字符串转数字 str_arr = np.array(['1.5', '2.5', '3.5']) num_arr = str_arr.astype(np.float64) print(num_arr) # [1.5 2.5 3.5]

类型转换注意事项

  • 浮点转整数: 会截断小数部分(不是四舍五入),2.9 转为 2,而不是 3
  • 溢出风险: 将大范围类型转为小范围类型时可能溢出,如 int64int8 会截断高位字节
  • 布尔转换: 00.0False,非零值全部转为 True
  • 精度损失: float64float32 会损失约7位有效数字的精度

五、数组维度操作

维度操作是NumPy中最实用且最常使用的功能之一,涵盖形状变换(reshape)、展平(ravel/flatten)和转置(transpose)等核心操作。掌握这些操作是进行数据预处理和矩阵运算的前提。

5.1 reshape — 重塑数组形状

reshape() 在不改变数据的前提下,改变数组的维度结构。新形状的元素总数必须与原数组相同。

# 一维转二维 arr = np.arange(12) reshaped = arr.reshape(3, 4) print(reshaped) # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] # 使用-1自动推断维度 auto = arr.reshape(2, -1) # -1自动计算为6 print(auto.shape) # (2, 6) # 一维转三维 arr3d = arr.reshape(2, 2, 3) print(arr3d) # [[[ 0 1 2] # [ 3 4 5]] # [[ 6 7 8] # [ 9 10 11]]] # reshape返回的是视图(view),修改会影响原数组 view = arr.reshape(3, 4) view[0, 0] = 999 print(arr[0]) # [999 1 2 3 4 5 6 7 8 9 10 11]

reshape的-1技巧

reshape 中使用 -1 作为某一维度的大小,NumPy会自动根据总元素数和其他维度值计算该维度的大小。例如 arr.reshape(-1, 1) 可以将一维数组转为列向量,arr.reshape(1, -1) 转为行向量。这是数据预处理中非常常用的技巧。

5.2 ravel / flatten — 数组展平

两者都将多维数组变为一维,区别在于 ravel() 返回的是原数组的视图(共享内存),而 flatten() 返回的是新分配的数组(独立内存)。

# 创建一个二维数组 arr2d = np.array([[1, 2, 3], [4, 5, 6]]) # ravel: 返回视图(不复制数据) r = arr2d.ravel() print(r) # [1 2 3 4 5 6] # flatten: 返回副本(分配新内存) f = arr2d.flatten() print(f) # [1 2 3 4 5 6] # 关键区别:修改ravel会影响原数组 r[0] = 100 print(arr2d) # [[100 2 3] # [ 4 5 6]] # 修改flatten不影响原数组 f[1] = 200 print(arr2d) # [[100 2 3] # [ 4 5 6]](不受影响) # 展平时的顺序控制 arr_c = np.array([[1, 2, 3], [4, 5, 6]]) print(arr_c.ravel(order='C')) # [1 2 3 4 5 6](行优先,默认) print(arr_c.ravel(order='F')) # [1 4 2 5 3 6](列优先)

5.3 transpose / .T — 转置

转置操作交换数组的维度。对于二维数组,transpose().T 会将行和列互换;对于高维数组,可以指定轴的排列顺序。

# 二维矩阵转置 mat = np.array([[1, 2, 3], [4, 5, 6]]) print(mat.T) # [[1 4] # [2 5] # [3 6]] # transpose() 与 .T 等价 print(np.all(mat.T == mat.transpose())) # True # 高维数组的转置:指定轴顺序 arr3d = np.arange(24).reshape(2, 3, 4) print(arr3d.shape) # (2, 3, 4) # 交换维度:将第0轴和第2轴互换 transposed = arr3d.transpose(2, 1, 0) print(transposed.shape) # (4, 3, 2) # 实际应用:图像数据的通道转置 # 假设图像数据形状为 (H, W, C) 转为 (C, H, W) img_hwc = np.random.rand(224, 224, 3) img_chw = img_hwc.transpose(2, 0, 1) print(img_chw.shape) # (3, 224, 224)

5.4 新增与删除维度

在实际的数据处理中,经常需要增加或减少数组的维度以满足特定函数的输入要求。

# np.newaxis: 增加新维度 arr = np.array([1, 2, 3]) col = arr[:, np.newaxis] # (3,) -> (3, 1) row = arr[np.newaxis, :] # (3,) -> (1, 3) print(col.shape) # (3, 1) print(row.shape) # (1, 3) # np.expand_dims: 在指定位置增加维度 expanded = np.expand_dims(arr, axis=1) print(expanded.shape) # (3, 1) # np.squeeze: 删除长度为1的维度 arr_sq = np.array([[[1], [2], [3]]]) print(arr_sq.shape) # (1, 3, 1) squeezed = np.squeeze(arr_sq) print(squeezed.shape) # (3,) # 只删除指定轴上的单维度 squeezed_axis = np.squeeze(arr_sq, axis=0) print(squeezed_axis.shape) # (3, 1)

六、综合案例实战

下面通过一个完整的综合案例,将本章学习的数组创建、属性查询和维度操作融会贯通。

案例:学生成绩数据处理

模拟一个班级的学生成绩数据,通过NumPy操作完成成绩创建、信息查询和数据重塑。

# 步骤1:创建成绩数据(5名学生,4门课程) np.random.seed(2024) scores = np.random.randint(50, 100, size=(5, 4)) print("成绩矩阵 (5x4):") print(scores) # 步骤2:查询数据基本属性 print(f"\n数据维度: {scores.ndim}") print(f"数据形状: {scores.shape}") print(f"数据类型: {scores.dtype}") print(f"数据总字节: {scores.nbytes} 字节") # 步骤3:计算每名学生的总分和平均分 total_scores = scores.sum(axis=1) mean_scores = scores.mean(axis=1) print(f"\n每名学生总分: {total_scores}") print(f"每名学生平均分: {mean_scores}") # 步骤4:计算每门课程的平均分 course_means = scores.mean(axis=0) print(f"每门课程平均分: {course_means}") # 步骤5:数据重塑 # 将成绩表展平为一维 flat_scores = scores.flatten() print(f"\n展平后的成绩: {flat_scores}") # 重塑为不同的结构 class1 = scores[:3, :].reshape(-1) class2 = scores[3:, :].reshape(-1) print(f"前三名学生成绩: {class1}") print(f"后两名学生成绩: {class2}") # 步骤6:成绩转置(课程作为行,学生作为列) scores_T = scores.T print(f"\n转置后形状: {scores_T.shape}") print("课程成绩分布:") print(scores_T)
成绩矩阵 (5x4): [[89 52 80 54] [67 94 50 91] [58 52 76 64] [53 50 52 71] [66 71 84 93]] 数据维度: 2 数据形状: (5, 4) 数据类型: int32 数据总字节: 80 字节 每名学生总分: [275 302 250 226 314] 每名学生平均分: [68.75 75.5 62.5 56.5 78.5 ] 每门课程平均分: [66.6 67.8 68.4 74.6] 展平后的成绩: [89 52 80 54 67 94 50 91 58 52 76 64 53 50 52 71 66 71 84 93] 前三名学生成绩: [89 52 80 54 67 94 50 91 58 52 76 64] 后两名学生成绩: [53 50 52 71 66 71 84 93] 转置后形状: (4, 5) 课程成绩分布: [[89 67 58 53 66] [52 94 52 50 71] [80 50 76 52 84] [54 91 64 71 93]]

通过这个案例可以看到,NumPy的数组操作使得批量数据处理变得异常简洁。如果使用纯Python的列表和循环来实现上述功能,代码量至少会多出3-5倍,而且执行速度也会慢很多。

七、常见错误与调试

易错点1:reshape的元素总数不匹配

这是用NumPy时最常见的错误之一。reshape要求新形状的元素总数与原数组完全一致,否则会抛出 ValueError

# 错误示例 arr = np.arange(10) # arr.reshape(3, 4) # 报错!10个元素无法重排为12个元素 # 正确做法:使用-1自动推断 reshaped = arr.reshape(2, -1) # 自动计算为5 print(reshaped.shape) # (2, 5)

易错点2:视图与副本的混淆

reshape()ravel() 返回的是视图,修改会影响原数组;flatten() 返回的是副本,修改不会影响原数组。如果不确定,使用 .copy() 显式创建副本。

arr = np.arange(6).reshape(2, 3) # 显式创建副本确保安全 safe_copy = arr.reshape(3, 2).copy()

易错点3:astype不修改原数组

astype() 返回新数组,不会修改原数组。如果需要覆盖原数组,必须重新赋值。

arr = np.array([1.5, 2.5, 3.5]) arr.astype(np.int32) # 没赋值,不影响原数组 print(arr.dtype) # float64(未改变) arr = arr.astype(np.int32) # 正确方式:重新赋值 print(arr.dtype) # int32

八、核心要点总结

九、进一步思考

掌握数组创建与属性是NumPy学习的第一步,也是最重要的一步。在此基础上可以进一步探索以下方向:

进阶学习路径

  • 数组运算: 向量化算术运算、广播机制、矩阵乘法(np.dot / @)、通用函数(ufunc)
  • 索引与切片: 花式索引、布尔索引、切片赋值、np.where() 条件筛选
  • 统计与聚合: sum()mean()std()min()max() 以及 axis 参数的深入理解
  • 文件IO: np.loadtxt()np.save()np.savetxt() 实现数据持久化
  • 线性代数: np.linalg 模块中的矩阵分解、特征值、线性方程组求解
  • 性能优化: 理解内存布局(C-order vs Fortran-order)、使用 np.einsum() 高效表达张量运算

NumPy的高性能计算能力远不止于此。在后续的学习中,建议结合实际的数据分析项目来练习,例如使用NumPy处理CSV数据、计算统计指标、实现简单的机器学习算法(如KNN、线性回归)等。只有通过大量的编码实践,才能真正掌握NumPy的精髓。

课后练习建议

  1. 创建一个形状为 (6, 8) 的随机整数数组,将其重塑为 (4, 12),再转置为 (12, 4)
  2. 创建两个形状不同的数组,使用 np.newaxis 使它们的形状兼容并做加法运算
  3. np.linspace(0, 100, 31) 生成的序列中,筛选出大于50的元素(提示:研究布尔索引)
  4. 对比使用Python列表解析和NumPy向量化计算100万个数的平方所需的时间,观察性能差异