当前位置:首页>python>NumPy从入门到精通:看完这篇,你的Python数据处理效率提升10倍!

NumPy从入门到精通:看完这篇,你的Python数据处理效率提升10倍!

  • 2026-09-02 19:30:51
NumPy从入门到精通:看完这篇,你的Python数据处理效率提升10倍!

很多人学NumPy只看代码不看文字,结果一知半解。本文反其道而行:每个代码示例前先讲“为什么”,代码后再说“是什么”,让你真正理解每一条指令。

一、NumPy是什么?为什么必须学?

NumPy是Python科学计算的基石。它的核心是一个叫做ndarray的多维数组对象。为什么不用Python自带的列表?因为列表里的元素可以是不同类型,每个元素都是一个独立的对象,内存不连续,循环遍历很慢。而NumPy数组所有元素类型相同,内存连续排列,并且大部分运算都是用C语言实现的,速度可以比列表快几十倍甚至上百倍。

下面我们创建第一个NumPy数组,看看它和列表有什么不同。

import numpy as np# 从普通列表创建NumPy数组py_list = [1, 2, 3]np_array = np.array(py_list)print(py_list)    # [1, 2, 3]  逗号分隔print(np_array)   # [1 2 3]     没有逗号,这是NumPy的打印风格print(type(np_array))  # <class 'numpy.ndarray'>

可以看到,NumPy数组的打印结果里元素之间只有空格,没有逗号。这个小小的视觉差异其实反映了底层存储的不同:列表是一个个Python对象指针的序列,而数组是一块连续的内存区域。

二、ndarray的三个“限制”(其实是性能的代价)

初学NumPy时,你可能会觉得它很死板。下面三个限制是初学者最容易抱怨的,但理解它们背后的原因,你就知道为什么NumPy这么快了。

限制一:所有元素必须类型相同

如果数组中混入了一个浮点数,整个数组都会变成浮点类型。这样做的好处是每个元素占用的字节数一致,CPU可以快速定位到第i个元素的地址:起始地址 + i × 元素字节数。

# 尝试混合整数和浮点数mixed = np.array([1, 2, 3.5])print(mixed)        # [1.  2.  3.5]  所有整数都变成了浮点数print(mixed.dtype)  # float64

限制二:数组总大小创建后不可变

你不能像列表那样append或pop。这是为了保持内存的连续性。如果需要动态增删元素,要么提前分配足够大的空间,要么先用列表收集数据最后再转成数组。

arr = np.array([1,2,3])# arr.append(4)   # 错误!ndarray没有append方法# 正确做法:先收集到列表,再转换data = [1,2,3]data.append(4)arr = np.array(data)print(arr)  # [1 2 3 4]

限制三:形状必须是矩形

二维数组的每一行必须有相同数量的列,不能出现“锯齿”。因为NumPy的索引计算依赖于规整的形状:访问(i,j)元素的偏移量 = i * 列数 + j。

# 下面这个会报错# bad = np.array([[1,2], [3,4,5]])  # ValueError: setting an array element with a sequence# 矩形数组没问题good = np.array([[1,2,3], [4,5,6]])print(good.shape)   # (2, 3)

三、必须掌握的数组属性(像体检报告一样重要)

拿到一个数组,首先要查看它的“体检报告”。下面这五个属性是最常用的。

a = np.array([[1, 2, 3], [4, 5, 6]])# 维度数(几维数组)print(a.ndim)      # 2# 形状(各维度的长度)print(a.shape)     # (2, 3)# 元素总个数print(a.size)      # 6# 元素的数据类型print(a.dtype)     # int64# 每个元素占用的字节数print(a.itemsize)  # 8 (因为int64是8字节)# 数组总共占用的字节数(等于 size * itemsize)print(a.nbytes)    # 48

当你拿到一个陌生的数组时,先用这些属性看看它的结构,可以避免很多错误。

四、创建数组的多种方法(不同场景用不同函数)

1. array 与 asarray:拷贝还是共享?

np.array()总是会复制输入数据,生成一个全新的数组。而np.asarray()如果输入已经是ndarray,就不会复制,直接共享内存。这在你需要避免不必要的内存复制时很有用。

original = np.array([1, 2, 3])# 使用 array:总是拷贝copy_arr = np.array(original)copy_arr[0] = 999print(original[0])   # 1 (原数组没变)# 使用 asarray:如果输入是ndarray,则共享内存view_arr = np.asarray(original)view_arr[0] = 888print(original[0])   # 888 (原数组也被改了)

如果输入是一个列表,asarray也会拷贝,因为列表不是ndarray。

lst = [1,2,3]arr1 = np.asarray(lst)   # 这里会拷贝,因为输入是列表arr1[0] = 777print(lst[0])            # 1 (列表没变)

2. zeros, ones, empty 以及它们的 _like 版本

创建全0数组、全1数组是非常常见的需求,比如初始化权重矩阵。

# 全0数组,默认float64类型zeros_arr = np.zeros((2, 3))print(zeros_arr)# [[0. 0. 0.]#  [0. 0. 0.]]# 全1数组ones_arr = np.ones((2, 3))print(ones_arr)# [[1. 1. 1.]#  [1. 1. 1.]]# empty数组:只分配内存,不初始化,里面的值是随机的(内存垃圾)empty_arr = np.empty((2, 3))print(empty_arr)# 运行结果每次都可能不同,比如:# [[-9.05243306e-312  1.06658093e-264  9.05246807e-312]#  [ 9.05246807e-312  6.91691904e-323  2.96439388e-323]]

empty比zeros快,因为它不需要花时间把每个元素设为0。但你必须确保之后会覆盖所有元素,否则读取到未初始化的垃圾值会导致难以调试的问题。

_like系列函数可以基于已有数组的形状和类型创建新数组,非常方便。

template = np.array([[1,2],[3,4]])like_zeros = np.zeros_like(template)   # 形状和类型与template相同like_ones = np.ones_like(template)print(like_zeros)   # [[0 0] [0 0]]  类型是int64,与template一致

3. full:用指定值填充

有时候你需要所有元素都是同一个非0非1的值,比如初始化偏置项为0.5。

full_arr = np.full((2, 4), 7)   # 2行4列,每个元素都是7print(full_arr)# [[7 7 7 7]#  [7 7 7 7]]

4. arange:类似range的数组版本

np.arange()和Python内置的range()用法几乎一样,但它返回的是ndarray而不是迭代器。

print(np.arange(5))          # [0 1 2 3 4]print(np.arange(2, 10, 2))   # [2 4 6 8]print(np.arange(0, 1, 0.3))  # [0.  0.3 0.6 0.9]  支持浮点步长

注意:当使用浮点步长时,由于浮点精度问题,arange可能包含的元素个数不是精确预期的。更安全的做法是用linspace。

5. linspace 和 logspace:等间隔与等比间隔

linspace在指定的区间内生成固定数量的等间距点,并且可以控制是否包含终点。这在绘图时非常有用。

# 从0到10,生成5个点(包含10)linear = np.linspace(0, 10, 5)print(linear)   # [ 0.   2.5  5.   7.5 10. ]# 不包含终点的情况linear_no_end = np.linspace(0, 10, 5, endpoint=False)print(linear_no_end)   # [0. 2. 4. 6. 8.]# 这里间隔 = (10-0)/5 = 2,所以得到0,2,4,6,8

logspace用于生成等比数列。它接收指数范围的起点和终点,以及底数。

# 底数为2,从2^2=4到2^5=32,取5个数log_vals = np.logspace(2, 5, 5, base=2)print(log_vals)# [ 4.          6.72717132 11.3137085  19.02731384 32.        ]# 可以看到,这些数在对数坐标上是均匀的

6. 随机数数组:模拟数据必备

NumPy的random模块提供了多种随机分布,可以用来生成测试数据或进行蒙特卡洛模拟。

# 在[0,1)区间均匀分布,形状(2,3)rand_uniform = np.random.rand(2, 3)print(rand_uniform)# 例如:[[0.77112868 0.97415392 0.25668864]#       [0.49946961 0.23491874 0.40514576]]# 随机整数,从0到10(不含10),形状(2,3)rand_int = np.random.randint(0, 10, (2, 3))print(rand_int)# 例如:[[7 8 2] [1 2 3]]# 在[3,6)区间均匀分布的浮点数rand_uniform_range = np.random.uniform(3, 6, (2, 3))print(rand_uniform_range)# 例如:[[5.69275495 3.84857937 3.2899215] [5.32035519 3.7460973 3.33859905]]# 标准正态分布 N(0,1)rand_normal = np.random.randn(2, 3)print(rand_normal)# 例如:[[-2.03654925 -0.50146561  0.4362483] [-1.90585739  0.94797017 -0.77026926]]

五、数据类型与转换(精确控制内存与精度)

创建数组时可以通过dtype参数指定类型。默认整数是int64,浮点是float64。如果你处理的数据量很大,可以使用float32来节省一半内存。

# 创建时指定类型arr_f32 = np.array([1, 2, 3], dtype=np.float32)print(arr_f32.dtype)   # float32# 使用类型代码arr_i8 = np.array([1,2,3], dtype='i8')   # i8 代表 int64print(arr_i8.dtype)    # int64

astype方法可以转换已有数组的类型。注意:浮点数转整数时会截断小数部分,不是四舍五入。

float_arr = np.array([1.9, 2.7, 3.2])int_arr = float_arr.astype(np.int64)print(int_arr)   # [1 2 3]  小数部分直接丢弃# 如果需要四舍五入,先用 np.rintrounded = np.rint(float_arr).astype(np.int64)print(rounded)   # [2 3 3]

六、切片和索引(小心视图!)

NumPy的切片语法和Python列表很像,但有一个关键区别:切片返回的是原数组的视图(view),而不是副本。这意味着修改切片会改变原数组。

# 一维数组切片arr = np.arange(10)   # [0 1 2 3 4 5 6 7 8 9]print(arr[2])         # 2print(arr[2:9:2])     # [2 4 6 8]print(arr[2:])        # [2 3 4 5 6 7 8 9]# 二维数组切片arr2d = np.array([[1,2,3], [4,5,6], [7,8,9]])print(arr2d[0, 1])    # 2  (第0行第1列)print(arr2d[:2, 1:])  # [[2 3] [5 6]]# 重要:切片是视图sub = arr2d[:2, :2]   # 左上角2x2区域sub[0,0] = 999print(arr2d[0,0])     # 999 (原数组被修改了!)

如果你需要一个真正的副本,请显式调用.copy()方法。

sub_copy = arr2d[:2, :2].copy()sub_copy[0,0] = 0print(arr2d[0,0])     # 仍然是999,因为副本不影响原数组

七、常用函数(每个函数都有例子)

7.1 基本数学函数

这些函数都是逐元素操作的。

arr = np.random.randn(2, 3)   # 标准正态分布随机数print("原始数组:\n", arr)# 绝对值print("绝对值:\n", np.abs(arr))# 向上取整(向正无穷)print("向上取整:\n", np.ceil(arr))# 向下取整(向负无穷)print("向下取整:\n", np.floor(arr))# 四舍五入到最近的整数print("四舍五入:\n", np.rint(arr))# 判断是否为NaNprint("是否为NaN:\n", np.isnan(arr))# 逐元素相乘a = np.array([1,2,3])b = np.array([4,5,6])print("逐元素相乘:", np.multiply(a, b))   # [4 10 18]# 逐元素相除print("逐元素相除:", np.divide(a, b))     # [0.25 0.4  0.5 ]# where:三元运算符的矢量化版本condition = np.array([-1, 2, -3, 4])result = np.where(condition > 0, 1, 0)print("where结果:", result)   # [0 1 0 1]

7.2 统计函数

统计函数默认计算整个数组。通过axis参数可以指定按行或按列计算。

arr = np.array([[1,2,3], [4,5,6]])print("全局平均值:", np.mean(arr))     # 3.5print("全局和:", np.sum(arr))         # 21print("全局最大值:", np.max(arr))     # 6print("全局最小值:", np.min(arr))     # 1print("全局标准差:", np.std(arr))     # 1.707825...print("全局方差:", np.var(arr))       # 2.916666...# axis=0 表示沿着第0维(行方向)操作,即按列计算print("按列求和:", np.sum(arr, axis=0))   # [5 7 9]print("按列平均值:", np.mean(arr, axis=0)) # [2.5 3.5 4.5]# axis=1 表示沿着第1维(列方向)操作,即按行计算print("按行求和:", np.sum(arr, axis=1))   # [6 15]print("按行平均值:", np.mean(arr, axis=1)) # [2. 5.]# 最大值、最小值的索引(全局)print("全局最大值索引:", np.argmax(arr))   # 5(展平后的位置)print("全局最小值索引:", np.argmin(arr))   # 0# 累加和(返回一维数组)print("累加和:", np.cumsum(arr))   # [ 1  3  6 10 15 21]# 累乘积(按行)print("按行累乘积:\n", np.cumprod(arr, axis=1))# [[ 1  2  6]#  [ 4 20 120]]

7.3 比较函数 any 和 all

arr = np.array([1,2,3,4,5])print("是否存在大于3的元素?", np.any(arr > 3))   # Trueprint("是否所有元素都大于3?", np.all(arr > 3))   # False

7.4 排序

NumPy提供了两种排序方式:原地排序和返回副本。

arr = np.random.randint(0, 10, (3, 3))print("原数组:\n", arr)# 原地排序,默认按最后一个轴(对于二维数组即按行)arr.sort()print("按行排序后(原地):\n", arr)# 按列排序(每一列独立排序)arr.sort(axis=0)print("按列排序后:\n", arr)# 不修改原数组的排序arr2 = np.random.randint(0, 10, (3, 3))sorted_arr = np.sort(arr2)   # 返回新数组print("原数组不变:\n", arr2)print("排序后的新数组:\n", sorted_arr)

7.5 去重

arr = np.random.randint(0, 5, (3, 3))print("原数组:\n", arr)unique_vals = np.unique(arr)print("唯一值(已排序):", unique_vals)# 同时返回频次vals, counts = np.unique(arr, return_counts=True)print("值:", vals)print("出现次数:", counts)

八、矢量化运算与广播(NumPy性能的核心)

8.1 数组与标量运算

标量会被“广播”到数组的每个元素上。

arr = np.array([[1,2,3],[4,5,6]])print(arr + 10)   # [[11 12 13] [14 15 16]]print(arr * 2)    # [[ 2  4  6] [ 8 10 12]]

8.2 相同形状数组之间的逐元素运算

a = np.array([1,2,3])b = np.array([4,5,6])print(a + b)   # [5 7 9]print(a * b)   # [4 10 18]  (注意这不是矩阵乘法)

8.3 广播:不同形状数组的运算

广播机制允许不同形状的数组进行运算,前提是它们可以“对齐”。对齐规则如下:

  1. 如果两个数组的维度数不同,则在维度较少的数组形状左边补1。
  2. 比较每个维度的大小:如果大小相同或者其中一个为1,则兼容;否则报错。
  3. 对于大小为1的维度,该维度会被拉伸(逻辑上复制)以匹配另一个数组的大小。

下面通过几个例子来理解。

例子1:一维数组 (3,) 与 二维数组 (3,1) 相加

row = np.array([1,2,3])          # 形状 (3,)col = np.array([[4],[5],[6]])    # 形状 (3,1)# 根据规则1:row的维度数少,左边补1,变成 (1,3)# 现在形状 (1,3) 和 (3,1)# 规则2:在维度0上,1和3,将row扩展为 (3,3)#        在维度1上,3和1,将col扩展为 (3,3)# 最终得到3x3的加法结果result = row + colprint(result)# [[5 6 7]#  [6 7 8]#  [7 8 9]]

例子2:行向量 (1,3) 与列向量 (2,1) 相加

row2 = np.array([[1,2,3]])      # 形状 (1,3)col2 = np.array([[4],[5]])      # 形状 (2,1)# 规则2:维度0上,1和2,将row2扩展为 (2,3)#       维度1上,3和1,将col2扩展为 (2,3)result2 = row2 + col2print(result2)# [[5 6 7]#  [9 10 11]]

例子3:无法广播的情况

a = np.array([1,2,3])   # (3,)b = np.array([4,5])     # (2,)# 两个一维数组,维度数相同,直接比较维度大小:3 vs 2,不相等且没有1,所以报错# a + b   # ValueError: operands could not be broadcast together with shapes (3,) (2,)

九、矩阵乘法(区分逐元素乘和真正的矩阵乘)

很多初学者会混淆*和@。记住:*是逐元素相乘,@是线性代数中的矩阵乘法。

# 逐元素乘法:形状必须相同或可广播A = np.array([[1,2],[3,4]])B = np.array([[5,6],[7,8]])print(A * B)    # [[ 5 12] [21 32]]  对应位置相乘# 矩阵乘法:第一个矩阵的列数必须等于第二个矩阵的行数C = np.array([[1,2,3],[4,5,6]])   # 2x3D = np.array([[6,5],[4,3],[2,1]]) # 3x2# 三种等价的写法print(C @ D)          # 推荐,最直观print(C.dot(D))print(np.dot(C, D))# 结果 [[20 14]#       [56 41]]# 手动验证第一个元素:C的第一行[1,2,3]点乘D的第一列[6,4,2] = 1*6+2*4+3*2=20# 第二个元素:第一行点乘第二列[5,3,1] = 1*5+2*3+3*1=14# 第三元素:第二行点乘第一列 = 4*6+5*4+6*2=56# 第四元素:第二行点乘第二列 = 4*5+5*3+6*1=41# 二维数组与一维数组的矩阵乘法:结果是一维数组e = np.array([6,5,4])   # 长度必须等于C的列数print(C @ e)            # [28 73]# 计算:第一行[1,2,3]点乘[6,5,4]=28;第二行[4,5,6]点乘[6,5,4]=73

总结

本文通过大量代码示例和逐段解释,覆盖了NumPy最核心的知识点:

  • 创建数组
    :array、asarray、zeros、ones、empty、full、arange、linspace、logspace、随机数函数。不同场景用不同函数,可以节省内存或提高可读性。
  • 数组属性
    :ndim、shape、size、dtype、itemsize、nbytes,拿到数组先看这些。
  • 数据类型
    :用dtype指定,用astype转换,注意浮点转整型是截断。
  • 切片与索引
    :切片返回视图,修改视图会影响原数组,需要副本时用.copy()。
  • 常用函数
    :数学函数(abs、ceil、floor、rint、where),统计函数(mean、sum、max、min、std、cumsum、argmax等),比较函数(any、all),排序(sort原地、np.sort副本),去重(unique)。
  • 广播机制
    :理解三条规则,让不同形状数组安全运算,避免报错。
  • 矩阵乘法
    :*是逐元素乘,@(或dot)是真正的矩阵乘。

学习建议:打开Jupyter Notebook,把本文的每个代码块手动敲一遍,修改参数看看结果变化,遇到不懂的地方再回头读解释。当你熟练了这些基础操作,就可以开始用NumPy处理真实数据,或者进一步学习Pandas、Matplotlib等库。祝你学习愉快!

最新文章

随机文章