Numpy 详解

一、Numpy优势

学习目标

  • 了解Numpy运算速度上的优势
  • 知道Numpy的数组内存块风格
  • 知道Numpy的并行化运算

1 Numpy介绍

Numpy(Numerical Python) 是一个开源的Python科学计算库,专门用于快速处理任意维度的数组

Numpy使用ndarray对象来处理多维数组,该对象是一个快速而灵活的大数据容器。相比Python原生的列表,Numpy在数值计算方面更加高效简洁。

import numpy as np

# 示例:创建一个二维数组(学生成绩表)
score = np.array([[80, 89, 86, 67, 79],
                  [78, 97, 89, 67, 81],
                  [90, 94, 78, 67, 74]])
print(score)
# 输出:
# [[80 89 86 67 79]
#  [78 97 89 67 81]
#  [90 94 78 67 74]]

2 ndarray介绍

NumPy provides an N-dimensional array type, the ndarray, which describes a collection of “items” of the same type.

NumPy提供了一个N维数组类型ndarray,它描述了相同类型的"items"的集合。

# nd= N-dimensional array (N维数组)
# 例如:2行3列的二维数组
arr_2d = np.array([[1, 2, 3],
                   [4, 5, 6]])
print(arr_2d.shape)  # (2, 3)

3 ndarray与Python原生list运算效率对比

Numpy专门针对ndarray的操作和运算进行了设计,数组的存储效率和输入输出性能远优于Python中的嵌套列表。数组越大,Numpy的优势就越明显

import random
import time
import numpy as np

# Python列表:1亿个随机数求和
a = [random.random() for i in range(100000000)]

start = time.time()
sum_list = sum(a)
print(f"Python列表求和耗时: {time.time() - start:.2f}秒")

# NumPy数组求和
b = np.array(a)
start = time.time()
sum_np = np.sum(b)
print(f"NumPy求和耗时: {time.time() - start:.2f}秒")
# 结果示例:Python约1.1秒,NumPy约0.1秒,快10倍以上

结论:在机器学习等需要大量数据运算的场景中,Numpy的高效性能至关重要。


4 ndarray的优势

4.1 内存块风格

存储方式 Python列表 ndarray
存储模式 分离式存储 一体式存储
元素类型 类型任意 类型必须相同
内存连续性 不连续,需寻址 连续存储,批量操作快
# Python列表:元素类型可以不同
py_list = [1, 2.5, "hello", True]  # 各种类型混搭

# NumPy数组:元素类型必须相同
np_arr = np.array([1, 2, 3, 4])     # 必须是同类型
print(np_arr.dtype)                  # int64

为什么连续存储更快?

  • 连续内存使CPU缓存预取更高效
  • 无需像列表那样通过指针链查找下一个元素
  • 可以使用SIMD指令集进行向量化计算

4.2 并行化运算(向量化运算)

Numpy内置并行运算功能,当系统有多个CPU核心时,会自动并行计算。

# Numpy自动使用多核并行计算
arr = np.random.rand(1000000)
result = np.sum(arr)  # 底层自动并行

4.3 底层C语言,高效GIL-free

Numpy底层使用C语言编写,**解除了GIL(全局解释器锁)**的限制,数组操作速度不受Python解释器限制,效率远高于纯Python代码。


5 小结

知识点 说明
Numpy定位 开源的Python科学计算库
核心对象 ndarray - N维数组类型
优势1 内存块风格 - 一体式存储,类型一致
优势2 并行化运算 - 自动多核并行
优势3 C语言底层 - 解除GIL,高效计算

二、N维数组-ndarray

学习目标

  • 说明数组的属性,形状、类型

1 ndarray的属性

属性 说明 示例
ndarray.shape 数组维度的元组 (3, 4) 表示3行4列
ndarray.ndim 数组维数 1表示一维,2表示二维
ndarray.size 数组中的元素总数量 12表示共12个元素
ndarray.itemsize 每个元素的字节大小 int64返回8
ndarray.dtype 数组元素的数据类型 int64, float32等
arr = np.array([[1, 2, 3, 4],
                [5, 6, 7, 8],
                [9, 10, 11, 12]])

print(arr.shape)      # (3, 4)  - 3行4列
print(arr.ndim)       # 2       - 二维数组
print(arr.size)       # 12      - 共12个元素
print(arr.itemsize)   # 8       - int64每个元素8字节
print(arr.dtype)      # int64   - 数据类型

2 ndarray的形状

一维数组

arr_1d = np.array([1, 2, 3, 4])
print(arr_1d.shape)  # (4,)

二维数组

arr_2d = np.array([[1, 2, 3],
                   [4, 5, 6]])
print(arr_2d.shape)  # (2, 3) - 2行3列

三维数组

arr_3d = np.array([[[1, 2, 3],
                    [4, 5, 6]],
                   [[7, 8, 9],
                    [10, 11, 12]]])
print(arr_3d.shape)  # (2, 2, 3) - 2个平面,每个平面2行3列

形象理解

一维数组 [1, 2, 3, 4]:
┌───┬───┬───┬───┐
│ 1 │ 2 │ 3 │ 4 │
└───┴───┴───┴───┘

二维数组 [[1, 2], [3, 4]]:
┌───┬───┐
│ 1 │ 2 │
├───┼───┤
│ 3 │ 4 │
└───┴───┘

三维数组:可以理解为多层二维数组的叠加

3 ndarray的类型(dtype)

类型代码 名称 描述 字节大小
'b' np.bool_ 布尔类型 1
'i' np.int8 8位整数 1
'i2' np.int16 16位整数 2
'i4' np.int32 32位整数(最常用) 4
'i8' np.int64 64位整数 8
'u' np.uint8 无符号8位整数 1
'u4' np.uint32 无符号32位整数 4
'f2' np.float16 半精度浮点 2
'f4' np.float32 单精度浮点 4
'f8' np.float64 双精度浮点(默认) 8
'c8' np.complex64 复数(两个float32) 8
'c16' np.complex128 复数(两个float64) 16
'O' np.object_ Python对象 -
'S' np.string_ 字节字符串 -
'U' np.unicode_ Unicode字符串 -

示例

# 创建时指定数据类型
arr_int32 = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
print(arr_int32.dtype)  # int32

# 创建字符串数组
arr_str = np.array(['hello', 'world'], dtype=np.string_)
print(arr_str)  # [b'hello' b'world']

# 默认类型
int_arr = np.array([1, 2, 3])      # 默认int64
float_arr = np.array([1.0, 2.0])   # 默认float64
print(int_arr.dtype, float_arr.dtype)  # int64 float64

4 总结

ndarray的核心属性:

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

print(arr.shape)      # (2, 3)    - 形状
print(arr.ndim)       # 2         - 维度
print(arr.size)       # 6         - 元素数
print(arr.dtype)      # float32   - 类型
print(arr.itemsize)   # 4         - 元素字节数

三、基本操作

学习目标

  • 理解数组的各种生成方法
  • 应用数组的索引机制实现数组的切片获取
  • 应用维度变换实现数组的形状改变
  • 应用类型变换实现数组类型改变

1 生成数组的方法

1.1 生成0和1的数组

# 生成全1数组
ones = np.ones((3, 4))           # 3行4列的全1数组
print(ones)
# [[1. 1. 1. 1.]
#  [1. 1. 1. 1.]
#  [1. 1. 1. 1.]]

# 指定数据类型
ones_int = np.ones((2, 3), dtype=np.int32)
print(ones_int.dtype)  # int32

# 生成全0数组
zeros = np.zeros((2, 3))
print(zeros)
# [[0. 0. 0.]
#  [0. 0. 0.]]

# 根据已有数组生成
arr = np.array([[1, 2], [3, 4]])
ones_like = np.ones_like(arr)   # 与arr形状相同的全1数组
zeros_like = np.zeros_like(arr) # 与arr形状相同的全0数组

应用场景

  • np.zeros() - 初始化数组、创建掩码
  • np.ones() - 创建全1数组、初始化权重

1.2 从现有数组生成

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

# 深拷贝 - 创建独立副本
a1 = np.array(a)
a1[0, 0] = 100
print(a[0, 0])  # 1,原始数组不受影响

# 浅拷贝 - 共享数据视图
a2 = np.asarray(a)
a2[0, 0] = 100
print(a[0, 0])  # 100,原始数组被修改

区别

  • np.array() - 总是复制数据(深拷贝)
  • np.asarray() - 如果输入已经是数组,不复制(浅拷贝)

1.3 生成固定范围的数组

np.linspace() - 等差数组(指定数量)
# 从0到10,生成5个等差数
arr = np.linspace(0, 10, 5)
print(arr)  # [ 0.   2.5  5.   7.5 10. ]

# 包括终点
arr = np.linspace(0, 100, 11)  # 0到100,生成11个数
print(arr)  # [  0.  10.  20.  30.  40.  50.  60.  70.  80.  90. 100.]

# 不包括终点
arr = np.linspace(0, 10, 5, endpoint=False)
print(arr)  # [0. 2. 4. 6. 8.]
np.arange() - 等差数组(指定步长)
# 从0到10,步长2
arr = np.arange(0, 10, 2)
print(arr)  # [0 2 4 6 8]

# 从1到10
arr = np.arange(1, 11)
print(arr)  # [ 1  2  3  4  5  6  7  8  9 10]

# 负数步长
arr = np.arange(10, 0, -1)
print(arr)  # [10  9  8  7  6  5  4  3  2  1]
np.logspace() - 等比数列
# 生成10^0到10^2(即1到100)的等比数列,共3个数
arr = np.logspace(0, 2, 3)
print(arr)  # [  1.  10. 100.]

# 常用于创建对数刻度的坐标
x = np.logspace(0, 3, 100)  # 10^0到10^3之间100个点

对比

函数 生成方式 适用场景
np.linspace(0, 10, 5) 指定数量,自动计算步长 需要精确控制点数
np.arange(0, 10, 2) 指定步长,自动计算数量 需要固定间隔
np.logspace(0, 2, 3) 等比数列 对数刻度坐标

1.4 生成随机数组

# 均匀分布 [0.0, 1.0)
np.random.rand(3, 2)           # 3行2列的随机数
np.random.rand(10)             # 10个随机数

# 均匀分布 [low, high)
np.random.randint(0, 10, size=(3, 4))  # 0到10之间,整数,3行4列
np.random.randint(1, 100, 10)         # 1到100之间,10个整数

# 标准正态分布(均值0,标准差1)
np.random.randn(100)            # 100个符合标准正态分布的数

# 正态分布(自定义均值和标准差)
np.random.normal(170, 5, (100,))  # 均值170,标准差5,100个数
np.random.normal(0, 1, (3, 3))   # 3x3的标准正态分布矩阵

# 设置随机种子(使结果可复现)
np.random.seed(42)
print(np.random.rand(5))  # 每次运行结果相同

2 数组的索引、切片

基本索引

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

# 索引格式:arr[行, 列]
print(arr[0, 0])    # 1    - 第1行第1列
print(arr[1, 2])    # 6    - 第2行第3列
print(arr[-1, -1])  # 9    - 最后一行最后一列

切片操作

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

# 切片格式:arr[起始:结束:步长, 起始:结束:步长]

# 取第1行
print(arr[0, :])     # [1 2 3 4]

# 取第1列
print(arr[:, 0])     # [1 5 9]

# 取前2行
print(arr[0:2, :])   # [[1 2 3 4]
                     #  [5 6 7 8]]

# 取前2列
print(arr[:, 0:2])   # [[ 1  2]
                     #  [ 5  6]
                     #  [ 9 10]]

# 跳过行/列
print(arr[::2, ::2]) # [[1 3]
                     #  [9 11]]

布尔索引

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

# 找出大于5的元素
mask = arr > 5
print(mask)
# [[False False False]
#  [False False  True]
#  [ True  True  True]]

print(arr[mask])  # [6 7 8 9]

# 直接赋值
arr[arr > 5] = 0
print(arr)
# [[1 2 3]
#  [4 5 0]
#  [0 0 0]]

三维数组索引

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

print(arr_3d.shape)  # (2, 2, 2)

# 索引格式:[平面, 行, 列]
print(arr_3d[0, 0, 0])  # 1  - 第1个平面,第1行,第1列
print(arr_3d[1, 1, 1])  # 8  - 第2个平面,第2行,第2列

# 取整个第1个平面
print(arr_3d[0])     # [[1 2]
                     #  [3 4]]

3 形状修改

reshape() - 返回视图

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

# 改为3行4列(元素总数不变)
new_arr = arr.reshape(3, 4)
print(new_arr)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

# -1自动计算维度
arr.reshape(-1, 4)   # 自动计算行数:3行4列
arr.reshape(3, -1)   # 自动计算列数:3行4列
arr.reshape(-1)      # 展平为一维

resize() - 修改原数组

arr = np.arange(12)
arr.resize(3, 4)  # 直接修改原数组
print(arr)
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

T(转置)

arr = np.arange(6).reshape(2, 3)
print(arr)
# [[0 1 2]
#  [3 4 5]]

print(arr.T)
# [[0 3]
#  [1 4]
#  [2 5]]

# 高维数组转置
arr_3d = np.arange(24).reshape(2, 3, 4)  # 2个平面,3行,4列
print(arr_3d.T.shape)  # (4, 3, 2) - 完全反转维度

4 类型修改

astype() - 类型转换

arr = np.array([1.5, 2.7, 3.3])

# 转为整数
int_arr = arr.astype(np.int32)
print(int_arr)  # [1 2 3]

# 转为字符串
str_arr = arr.astype(str)
print(str_arr)  # ['1.5' '2.7' '3.3']

# 转为布尔
bool_arr = np.array([0, 1, 2, 3]).astype(bool)
print(bool_arr)  # [False  True  True  True]

tobytes() - 转为字节串

arr = np.array([[1, 2], [3, 4]])
bytes_data = arr.tobytes()
print(bytes_data)  # b'\x01\x00\x00\x00...'

应用:网络传输、数据持久化


5 数组去重

np.unique()

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

# 基本去重
unique_vals = np.unique(arr)
print(unique_vals)  # [1 2 3 4]

# 二维数组去重(自动展平)
arr_2d = np.array([[1, 2, 3], [2, 3, 4]])
print(np.unique(arr_2d))  # [1 2 3 4]

# 返回索引
arr = np.array(['b', 'a', 'c', 'b'])
unique, indices = np.unique(arr, return_index=True)
print(indices)  # [1 0 2] - 首次出现的位置

# 返回出现次数
unique, counts = np.unique(arr, return_counts=True)
print(counts)  # [2 1 1] - 每个值出现的次数

6 小结

功能 函数 说明
生成0/1 np.ones() / np.zeros() 创建全0或全1数组
拷贝 np.array() / np.asarray() 深拷贝 vs 浅拷贝
范围数组 np.linspace() / np.arange() 等差数列
随机数组 np.random.rand() / randint() / normal() 随机数生成
索引切片 arr[行, 列] 先行后列
形状修改 reshape() / resize() / .T 形状变换
类型修改 astype() 类型转换
去重 np.unique() 去除重复元素

四、ndarray运算

学习目标

  • 应用数组的通用判断函数
  • 应用np.where实现数组的三元运算

1 逻辑运算

score = np.random.randint(40, 100, (10, 5))  # 10名学生,5门课成绩
print(score)
# [[52 85 67 73 91]
#  [88 92 45 78 65]
#  ...]

# 逻辑判断:成绩大于60
print(score > 60)
# [[False  True  True  True  True]
#  [ True  True False  True  True]
#  ...]

# 布尔索引赋值:大于60的置为1
score[score > 60] = 1
print(score)
# [[52  1  1  1  1]
#  [ 1  1 45  1  1]
#  ...]

2 通用判断函数

np.all() - 所有元素都满足条件

# 判断所有成绩是否都大于60
score = np.random.randint(40, 100, (5, 3))
result = np.all(score > 60)
print(f"全部及格?{result}")  # False 或 True

# 按维度判断
print(np.all(score > 60, axis=0))  # 每门课是否全部及格
print(np.all(score > 60, axis=1))  # 每个学生是否全部及格

np.any() - 至少有一个元素满足条件

# 判断是否有90分以上的成绩
result = np.any(score > 90)
print(f"有90分以上?{result}")  # False 或 True

# 按维度判断
print(np.any(score > 90, axis=0))  # 哪几门课有90分以上的
print(np.any(score > 90, axis=1))  # 哪些学生有90分以上的

区别

函数 逻辑 返回True的条件
np.all() 全真才真 所有元素都满足条件
np.any() 有真即真 至少有一个元素满足条件

3 np.where(三元运算符)

基本用法

temp = np.array([58, 72, 85, 91, 45])

# 条件满足返回1,否则返回0
result = np.where(temp > 60, 1, 0)
print(result)  # [0 1 1 1 0]

复合逻辑

# np.logical_and - 逻辑与
np.where(np.logical_and(temp > 60, temp < 90), 1, 0)
# 结果:[0 1 1 0 0] - 60到90之间(不含)的为1

# np.logical_or - 逻辑或
np.where(np.logical_or(temp > 90, temp < 60), 1, 0)
# 结果:[1 0 0 1 1] - 大于90或小于60的为1

# np.logical_not - 逻辑非
np.where(np.logical_not(temp > 60), 1, 0)
# 结果:[1 0 0 0 1] - 不大于60的为1

嵌套where

# 成绩分级:<60不及格,60-80及格,>80优秀
temp = np.array([45, 72, 85, 91])

result = np.where(temp < 60, '不及格',
           np.where(temp < 80, '及格', '优秀'))
print(result)  # ['不及格' '及格' '优秀' '优秀']

4 统计运算

基本统计函数

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

print(np.min(arr))        # 1  - 最小值
print(np.max(arr))        # 9  - 最大值
print(np.mean(arr))       # 5.0 - 平均值
print(np.median(arr))     # 5.0 - 中位数
print(np.std(arr))        # 2.58... - 标准差
print(np.var(arr))        # 6.67... - 方差

按维度统计

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

# axis=0:沿着列操作(跨行)
print(np.min(arr, axis=0))   # [1 2 3] - 每列的最小值

# axis=1:沿着行操作(跨列)
print(np.min(arr, axis=1))   # [2 1] - 每行的最小值

argmax/argmin - 索引位置

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

print(np.argmax(arr))  # 5 - 最大值9的下标
print(np.argmin(arr))  # 1 - 最小值1的下标

# 二维数组
arr_2d = np.array([[4, 2, 5],
                   [1, 8, 3]])
print(np.argmax(arr_2d, axis=0))  # [0 1 0] - 每列最大值的行索引
print(np.argmax(arr_2d, axis=1))  # [2 1] - 每行最大值的列索引

常用统计指标说明

指标 说明 公式
均值(mean) 所有数值的平均值 sum/n
中位数(median) 排序后位于中间的数 -
方差(var) 数值与均值的平方偏差 sum((x-mean)²)/n
标准差(std) 方差的平方根 sqrt(var)

方差 vs 标准差

  • 方差越大,数据越分散
  • 标准差与原数据单位相同,更易理解

5 小结

运算类型 函数 说明
逻辑运算 arr > 60 返回布尔数组
布尔赋值 arr[arr > 60] = 1 条件赋值
全真判断 np.all() 所有元素都满足
有真判断 np.any() 至少一个满足
三元运算 np.where() 条件赋值
复合逻辑 np.logical_and/or/not 逻辑组合
统计函数 min/max/mean/median/std/var 基础统计
索引函数 argmax/argmin 最大/小值位置

五、数组间运算

学习目标

  • 知道数组与数之间的运算
  • 知道数组与数组之间的运算
  • 说明数组间运算的广播机制

1 数组与数的运算

数组与标量(单个数字)运算时,每个元素都分别进行运算

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

print(arr + 1)    # [[2 3 4]
                  #  [5 6 7]]
print(arr - 1)    # [[0 1 2]
                  #  [3 4 5]]
print(arr * 2)    # [[2 4 6]
                  #  [8 10 12]]
print(arr / 2)    # [[0.5 1.  1.5]
                  #  [2.  2.5 3. ]]
print(arr ** 2)   # [[ 1  4  9]
                  #  [16 25 36]]
print(arr % 2)    # [[1 0 1]
                  #  [0 1 0]]

2 数组与数组的运算

形状相同,直接运算

a = np.array([[1, 2, 3],
              [4, 5, 6]])
b = np.array([[10, 20, 30],
              [40, 50, 60]])

print(a + b)      # [[11 22 33]
                  #  [44 55 66]]
print(a * b)      # [[ 10  40  90]
                  #  [160 250 360]]

形状不同,需要广播


3 广播机制

什么是广播?

当两个数组形状不同时,NumPy会自动将较小的数组"广播"到较大数组的形状,使它们能够进行运算。

广播规则

规则1:维度数不同,在前面补1维

a = np.array([[1, 2, 3]])     # shape: (1, 3)
b = np.array([[1], [2], [3]]) # shape: (3, 1)

# a 广播为 (3, 3)
# b 广播为 (3, 3)
print(a + b)
# [[2 3 4]
#  [3 4 5]
#  [4 5 6]]

规则2:从最后一个维度开始比较

a = np.array([[1, 2, 3]])     # shape: (1, 3)
b = np.array([[1], [2], [3]]) # shape: (3, 1)

# 从后向前比较:
# 第2维:3 vs 1 → 可以广播(1会扩展为3)
# 第1维:1 vs 3 → 可以广播(1会扩展为3)
# 结果形状:(3, 3)

规则3:维度长度必须相同或为1

# 可以广播的例子
a = np.ones((3, 4))  # shape: (3, 4)
b = np.ones((1, 4))  # shape: (1, 4) → 广播为 (3, 4)

# 可以广播的例子
a = np.ones((3, 4))  # shape: (3, 4)
b = 5                 # 标量 → 广播为 (3, 4)

# 不能广播的例子
a = np.ones((3, 4))  # shape: (3, 4)
b = np.ones((3, 5))  # shape: (3, 5) → 4≠5,报错

广播示例

# 示例1:行向量与列向量相加
row = np.array([1, 2, 3])       # shape: (3,)
col = np.array([[1], [2], [3]]) # shape: (3, 1)
print(row + col)
# [[2 3 4]
#  [3 4 5]
#  [4 5 6]]

# 示例2:矩阵与标量相加
mat = np.array([[1, 2, 3],
                [4, 5, 6]])
print(mat + 10)
# [[11 12 13]
#  [14 15 16]]

# 示例3:三维数组广播
a = np.ones((2, 3, 4))  # shape: (2, 3, 4)
b = np.ones((3, 4))     # shape: (3, 4) → 广播为 (2, 3, 4)
c = np.ones((1, 4))     # shape: (1, 4) → 广播为 (2, 3, 4)

形象理解广播

a (3x1):           b (1x3):          a + b (3x3):
┌───┐         ┌───┬───┬───┐      ┌───┬───┬───┐
│ 1 │         │ 1 │ 2 │ 3 │      │ 2 │ 3 │ 4 │
├───┤         └───┴───┴───┘      ├───┼───┼───┤
│ 2 │    +                   =    │ 3 │ 4 │ 5 │
├───┤                            │ 4 │ 5 │ 6 │
│ 3 │                            └───┴───┴───┘
└───┘

4 小结

运算类型 说明
数组+标量 每个元素与标量运算
数组+数组(形状相同) 对应元素直接运算
数组+数组(形状不同) 自动广播,维度长度必须相同或为1

广播的核心:将较小的数组在缺失的维度上复制扩展,直到与较大数组形状匹配。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐