欢迎光临
我们一直在努力

Numpy 详解

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)) # 3×3的标准正态分布矩阵

# 设置随机种子(使结果可复现)
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 (3×1): b (1×3): a + b (3×3):
┌───┐ ┌───┬───┬───┐ ┌───┬───┬───┐
│ 1 │ │ 1 │ 2 │ 3 │ │ 2 │ 3 │ 4 │
├───┤ └───┴───┴───┘ ├───┼───┼───┤
│ 2 │ + = │ 3 │ 4 │ 5 │
├───┤ │ 4 │ 5 │ 6 │
│ 3 │ └───┴───┴───┘
└───┘


4 小结

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

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

赞(0)
未经允许不得转载:171主机测评 » Numpy 详解
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址