深入解析NumPy中数组的核心概念与操作技巧
本节学习目标
完成本节学习后,你将能够:
- 理解 NumPy 数组(ndarray)与 Python 原生列表的本质区别
- 熟练创建各种类型的 NumPy 数组
- 掌握 NumPy 的常用数据类型及其转换方法
- 进行数组的基本运算和聚合操作
- 理解并运用广播机制
- 使用常用数学和统计函数处理数据
为什么学这个
如果把数据分析比作盖房子,那么 NumPy 就是砖块和水泥——它是最基础、最重要的材料。
你可能听说过 Python 的列表(list)也能存数据,那为什么还要学 NumPy?答案有两个字:速度和功能。
速度对比:对 100 万个数字求和,Python 列表需要约 50 毫秒,NumPy 数组只需要约 1 毫秒——快了 50 倍。如果数据量更大,差距会更惊人。
功能对比:Python 列表只能做简单的存储和遍历,而 NumPy 数组支持矩阵运算、统计分析、线性代数等高级操作。
比喻:Python 列表像一个普通收纳盒,什么都能装,但找东西慢;NumPy 数组像一个智能收纳柜,不仅存得快,还能自动帮你分类、计算和查找。
事实上,几乎所有主流的数据分析库(pandas、scikit-learn、SciPy、TensorFlow)底层都依赖 NumPy。学好 NumPy,就等于掌握了整个数据分析生态的"通用语言"。
认识 ndarray——NumPy 的核心数据结构
什么是 ndarray
ndarray(N-dimensional array)是 NumPy 的核心对象,即 N 维数组。它是一个存储同类型数据的多维容器。
与 Python 列表的区别:
| 特性 | Python list | NumPy ndarray |
|---|---|---|
| 数据类型 | 可以混合 | 必须统一 |
| 运算方式 | 需要循环 | 支持向量化 |
| 内存占用 | 较大 | 紧凑 |
| 执行速度 | 较慢 | 极快 |
| 功能 | 基础 | 丰富 |
为什么 ndarray 这么快
- 内存连续存储:所有元素在内存中紧挨着存放,CPU 可以高效读取(类似数组的"缓存友好"特性)
- C 语言底层实现:核心运算由 C 语言编写,绕过了 Python 的解释器开销
- 并行计算优化:底层利用了 CPU 的 SIMD(单指令多数据)指令集
ndarray 的三个核心属性
import numpy as np
# 创建一个二维数组
arr = np.array([[1, 2, 3],
[4, 5, 6]])
print("数组内容:")
print(arr)
print(f"\n维度数量 (ndim): {arr.ndim}")
print(f"形状 (shape): {arr.shape}")
print(f"元素总数 (size): {arr.size}")
print(f"数据类型 (dtype): {arr.dtype}")
print(f"每个元素占字节数 (itemsize): {arr.itemsize}")
print(f"总内存占用 (nbytes): {arr.nbytes}")
输出结果:
数组内容:
[[1 2 3]
[4 5 6]]
维度数量 (ndim): 2
形状 (shape): (2, 3)
元素总数 (size): 6
数据类型 (dtype): int32 (或 int64,取决于系统)
每个元素占字节数 (itemsize): 4
总内存占用 (nbytes): 24
理解 shape:(2, 3) 表示这个数组有 2 行、3 列。类比Excel表格,就是 2 行 3 列的数据区域。
NumPy 数组的创建方式
方式1:从 Python 列表/元组创建
# 从列表创建一维数组
arr1 = np.array([1, 2, 3, 4, 5])
print("一维数组:", arr1)
# 从嵌套列表创建二维数组
arr2 = np.array([[1, 2, 3],
[4, 5, 6]])
print("二维数组:\n", arr2)
# 从元组创建
arr3 = np.array((10, 20, 30))
print("从元组创建:", arr3)
# 指定数据类型
arr4 = np.array([1, 2, 3], dtype=np.float64)
print("指定浮点类型:", arr4)
方式2:使用内置函数创建特殊数组
# 全零数组
zeros = np.zeros((3, 4))
print("3x4 全零数组:\n", zeros)
# 全一数组
ones = np.ones((2, 3))
print("2x3 全一数组:\n", ones)
# 指定值的数组
full = np.full((2, 2), 7)
print("2x2 填充7的数组:\n", full)
# 单位矩阵(对角线为1,其余为0)
eye = np.eye(3)
print("3x3 单位矩阵:\n", eye)
# 对角矩阵
diag = np.diag([1, 2, 3])
print("对角矩阵:\n", diag)
方式3:生成数值序列
# arange:类似 range,但返回数组
print("arange(10):", np.arange(10))
print("arange(2, 10, 2):", np.arange(2, 10, 2))
# 输出: [2 4 6 8]
# linspace:生成等间距数列(包含终点)
print("linspace(0, 1, 5):", np.linspace(0, 1, 5))
# 输出: [0. 0.25 0.5 0.75 1. ]
# logspace:生成等比数列
print("logspace(0, 2, 5):", np.logspace(0, 2, 5))
# 输出: [ 1. 3.16 10. 31.6 100. ]
# 实际应用场景:生成 x 轴坐标用于画图
x = np.linspace(-np.pi, np.pi, 100) # -π 到 π,100个点
print(f"用于绘图的 x 轴: {len(x)} 个点")
方式4:生成随机数组
np.random.seed(42) # 设置随机种子,保证结果可复现
# 均匀分布 [0, 1)
rand_uniform = np.random.rand(3, 3)
print("均匀分布随机数组:\n", rand_uniform)
# 标准正态分布(均值0,标准差1)
rand_normal = np.random.randn(3, 3)
print("标准正态分布:\n", rand_normal)
# 指定范围的整数
rand_int = np.random.randint(1, 100, size=(3, 4))
print("1~99 的随机整数:\n", rand_int)
# 从指定数组中随机选择
choices = np.random.choice(['A', 'B', 'C', 'D'], size=10)
print("随机选择:", choices)
# 实际应用:模拟掷骰子 1000 次
dice_rolls = np.random.randint(1, 7, size=1000)
print(f"骰子平均点数: {dice_rolls.mean():.2f}") # 理论值应接近 3.5
实际场景:随机数在数据分析中用途广泛——模拟数据、A/B 测试、随机抽样、机器学习中的权重初始化等。
NumPy 数据类型
常用数据类型一览
# 整数类型 print(np.int8(127)) # -128 ~ 127 print(np.int16(32767)) # -32768 ~ 32767 print(np.int32(2147483647)) print(np.int64(9223372036854775807)) # 无符号整数(只存正数) print(np.uint8(255)) # 0 ~ 255 # 浮点数类型 print(np.float16(3.14)) # 半精度,范围小 print(np.float32(3.14159)) print(np.float64(3.14159265358979)) # 默认浮点类型 # 布尔类型 print(np.bool_(True)) # 字符串类型 print(np.array(['hello', 'world'], dtype='U5')) # 最多5个字符
数据类型转换
arr = np.array([1, 2, 3, 4, 5])
print(f"原始类型: {arr.dtype}") # int32 或 int64
# 转换为浮点数
arr_float = arr.astype(np.float64)
print(f"转换为浮点: {arr_float.dtype}")
# 转换为布尔值
arr_bool = arr.astype(bool)
print(f"转换为布尔: {arr_bool}")
# 转换为字符串
arr_str = arr.astype(str)
print(f"转换为字符串: {arr_str}")
# 注意:精度丢失
arr_big = np.array([1.7, 2.9, 3.1])
arr_int = arr_big.astype(np.int32)
print(f"浮点转整数(截断): {arr_int}") # [1 2 3],注意不是四舍五入
数组的基本运算
算术运算
a = np.array([10, 20, 30, 40])
b = np.array([1, 2, 3, 4])
# 逐元素运算
print("加法:", a + b) # [11 22 33 44]
print("减法:", a - b) # [ 9 18 27 36]
print("乘法:", a * b) # [10 40 90 160]
print("除法:", a / b) # [10. 10. 10. 10.]
print("整除:", a // b) # [10 10 10 10]
print("取余:", a % b) # [0 0 0 0]
print("幂运算:", a ** 2) # [ 100 400 900 1600]
# 数组与标量运算
print("加5:", a + 5) # [15 25 35 45]
print("乘2:", a * 2) # [20 40 60 80]
比较运算
a = np.array([10, 20, 30, 40, 50])
# 比较运算返回布尔数组
print("大于25:", a > 25) # [False False True True True]
print("等于30:", a == 30) # [False False True False False]
print("不等于20:", a != 20) # [ True False True True True]
print("在20~40之间:", (a >= 20) & (a <= 40))
# [False True True True False]
聚合(统计)运算
scores = np.array([85, 92, 78, 96, 88, 73, 95, 81])
print(f"总分: {scores.sum()}")
print(f"平均分: {scores.mean()}")
print(f"标准差: {scores.std():.2f}")
print(f"方差: {scores.var():.2f}")
print(f"最高分: {scores.max()}")
print(f"最低分: {scores.min()}")
print(f"最高分索引: {scores.argmax()}")
print(f"最低分索引: {scores.argmin()}")
print(f"中位数: {np.median(scores)}")
print(f"累计和: {scores.cumsum()}")
多维数组的轴(axis)概念
matrix = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print("原始矩阵:")
print(matrix)
# axis=0 表示按列(沿着行的方向)
print("\n按列求和 (axis=0):", matrix.sum(axis=0)) # [12 15 18]
# axis=1 表示按行(沿着列的方向)
print("按行求和 (axis=1):", matrix.sum(axis=1)) # [ 6 15 24]
# 不指定 axis 表示对所有元素
print("全部求和:", matrix.sum()) # 45
# 其他聚合函数同样支持 axis
print("\n每列最大值:", matrix.max(axis=0))
print("每行平均值:", matrix.mean(axis=1))
理解 axis 的技巧:axis=N 表示"沿着第 N 个维度进行压缩"。axis=0 就是把"行"压扁,结果是每列的统计值;axis=1 就是把"列"压扁,结果是每行的统计值。就像用复印机扫描,扫描方向就是被压扁的方向。
数组的索引与切片
一维数组索引
arr = np.array([10, 20, 30, 40, 50, 60])
# 正向索引(从 0 开始)
print("第1个元素:", arr[0]) # 10
print("第3个元素:", arr[2]) # 30
# 负向索引(从 -1 开始,表示倒数)
print("最后一个元素:", arr[-1]) # 60
print("倒数第2个:", arr[-2]) # 50
# 切片 [start:stop:step]
print("前3个:", arr[:3]) # [10 20 30]
print("从第3个开始:", arr[2:]) # [30 40 50 60]
print("每隔一个:", arr[::2]) # [10 30 50]
print("反转:", arr[::-1]) # [60 50 40 30 20 10]
二维数组索引
matrix = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])
# [行, 列] 索引
print("第2行第3列:", matrix[1, 2]) # 7
# 行切片
print("第1行:", matrix[0]) # [1 2 3 4]
print("前两行:\n", matrix[:2])
# 列切片
print("第2列:", matrix[:, 1]) # [2 6 10]
print("第2~3列:\n", matrix[:, 1:3])
# 行列同时切片
print("中间2x2区域:\n", matrix[1:3, 1:3])
# [[ 6 7]
# [10 11]]
# 重要:切片返回的是视图(共享内存),修改会影响原数组
sub = matrix[:2, :2]
sub[0, 0] = 999
print("修改切片后原数组:\n", matrix)
# 第1行第1列变为 999
# 如果需要副本,使用 .copy()
sub_copy = matrix[:2, :2].copy()
sub_copy[0, 0] = 0
print("修改副本后原数组不变:", matrix[0, 0]) # 仍是 999
广播机制(Broadcasting)——NumPy 的魔法
什么是广播
广播是 NumPy 对不同形状数组进行运算时的一种自动扩展机制。它让不同大小的数组也能"配对"运算。
比喻:广播就像拼图时的"自动适配"——如果你有一块大画布和一个小印章,广播会自动把印章在画布上重复盖,直到覆盖整个画布。
广播规则
- 如果两个数组维度数不同,在维度较少的数组前面补 1
- 如果某个维度上大小不同,且其中一个为 1,则该维度可扩展以匹配另一个
- 如果某个维度上大小不同,且都不为 1,则无法广播(报错)
代码示例
# 场景1:数组 + 标量(最简单的广播)
arr = np.array([1, 2, 3])
print("数组 + 10:", arr + 10) # [11 12 13]
# 内部过程: 10 被广播为 [10, 10, 10]
# 场景2:二维数组 + 一维数组
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
row_add = np.array([10, 20, 30])
print("每行加 [10,20,30]:")
print(matrix + row_add)
# [[11 22 33]
# [14 25 36]]
# 场景3:列向量 + 行向量(生成矩阵)
col = np.array([[1], [2], [3]]) # 形状 (3, 1)
row = np.array([10, 20, 30]) # 形状 (3,)
print("列向量 + 行向量 (广播生成矩阵):")
print(col + row)
# [[11 21 31]
# [12 22 32]
# [13 23 33]]
# 场景4:实际应用——成绩标准化
scores = np.array([[85, 90, 78],
[92, 88, 95],
[76, 82, 88]])
mean_per_subject = scores.mean(axis=0) # 每科平均分
print("每科平均分:", mean_per_subject)
# 每个学生每科减去该科平均分(广播)
centered = scores - mean_per_subject
print("中心化后的分数:\n", np.round(centered, 1))
广播失败的情况
# 这种情况无法广播
try:
a = np.array([[1, 2, 3], # 形状 (2, 3)
[4, 5, 6]])
b = np.array([1, 2]) # 形状 (2,)
result = a + b
except ValueError as e:
print("广播失败:", e)
# 原因: 最后一维度 3 != 2,且都不是 1
常用数学函数
x = np.array([0, np.pi/6, np.pi/4, np.pi/3, np.pi/2])
# 三角函数
print("sin(x):", np.sin(x))
print("cos(x):", np.round(np.cos(x), 4))
print("tan(x):", np.round(np.tan(x), 4))
# 指数和对数
print("\ne^x:", np.round(np.exp(x), 4))
print("ln(x+1):", np.round(np.log(x + 1), 4))
print("log10(x+1):", np.round(np.log10(x + 1), 4))
# 舍入函数
numbers = np.array([1.2, 2.5, 3.7, 4.49, 5.5])
print("\n原始数据:", numbers)
print("四舍五入:", np.round(numbers))
print("向上取整:", np.ceil(numbers))
print("向下取整:", np.floor(numbers))
print("截断取整:", np.trunc(numbers))
# 绝对值和符号
neg = np.array([-3, -2, -1, 0, 1, 2, 3])
print("\n绝对值:", np.abs(neg))
print("符号函数:", np.sign(neg))
代码示例:综合实战
实战1:学生成绩分析系统
import numpy as np
# 模拟一个班级30名学生、5门课程的成绩
np.random.seed(42)
n_students = 30
n_subjects = 5
subjects = ['语文', '数学', '英语', '物理', '化学']
# 生成成绩(正态分布,均值70,标准差15)
scores = np.random.normal(70, 15, (n_students, n_subjects))
scores = np.clip(scores, 0, 100) # 限制在0~100之间
scores = np.round(scores, 1)
print("=" * 50)
print("学生成绩分析系统")
print("=" * 50)
# 1. 每科的基本统计
print("\n各科统计:")
for i, subject in enumerate(subjects):
col = scores[:, i]
print(f" {subject}: 平均={col.mean():.1f}, "
f"最高={col.max():.1f}, 最低={col.min():.1f}, "
f"标准差={col.std():.1f}")
# 2. 每个学生的总分和平均分
total_scores = scores.sum(axis=1)
avg_scores = scores.mean(axis=1)
print(f"\n班级总平均分: {avg_scores.mean():.1f}")
print(f"最高总分: {total_scores.max():.1f}")
print(f"最低总分: {total_scores.min():.1f}")
# 3. 找出每科最高分的学生
for i, subject in enumerate(subjects):
best_student = scores[:, i].argmax()
print(f" {subject}最高分: 学生{best_student+1}号 ({scores[best_student, i]}分)")
# 4. 成绩分布分析
print(f"\n成绩等级分布:")
def get_grade(score):
if score >= 90: return '优秀'
elif score >= 80: return '良好'
elif score >= 60: return '及格'
else: return '不及格'
grades_flat = np.array([get_grade(s) for s in scores.flatten()])
for grade in ['优秀', '良好', '及格', '不及格']:
count = (grades_flat == grade).sum()
pct = count / len(grades_flat) * 100
print(f" {grade}: {count}人 ({pct:.1f}%)")
实战2:矩阵运算——图像灰度处理
import numpy as np
# 模拟一张 4x4 的 RGB 图像(每个像素有 R, G, B 三个通道)
# 形状: (高, 宽, 通道数)
np.random.seed(42)
image = np.random.randint(0, 256, (4, 4, 3), dtype=np.uint8)
print("原始RGB图像 (4x4):")
print(image[:, :, 0]) # 显示 R 通道
# 灰度转换公式: Gray = 0.299*R + 0.587*G + 0.114*B
weights = np.array([0.299, 0.587, 0.114])
# 使用点积进行灰度转换(广播)
gray = np.dot(image, weights)
gray = np.round(gray).astype(np.uint8)
print("\n灰度图像:")
print(gray)
# 二值化处理:设定阈值128
binary = (gray > 128).astype(np.uint8) * 255
print("\n二值化图像 (阈值128):")
print(binary)
实战练习
练习1:数组创建基础
题目:创建以下数组:
- 一个包含 0~9 的一维数组
- 一个 3 行 4 列的全 1 数组
- 一个 4 行 4 列的单位矩阵
- 1~100 中所有 3 的倍数构成的数组
参考答案:
# 1. 0~9 arr1 = np.arange(10) print(arr1) # 2. 3x4 全1 arr2 = np.ones((3, 4)) print(arr2) # 3. 4x4 单位矩阵 arr3 = np.eye(4) print(arr3) # 4. 1~100中3的倍数 multiples = np.arange(3, 101, 3) print(multiples)
练习2:统计分析
题目:某店铺一周七天的营业额为 [1200, 1500, 1800, 1350, 2100, 2800, 2500],请计算:
- 日均营业额
- 营业额的标准差(波动程度)
- 哪几天高于日均?
- 如果保持日均水平,一个月(30天)预计收入多少?
参考答案:
revenue = np.array([1200, 1500, 1800, 1350, 2100, 2800, 2500])
# 1. 日均
daily_avg = revenue.mean()
print(f"日均营业额: {daily_avg:.2f}元")
# 2. 标准差
std = revenue.std()
print(f"营业额标准差: {std:.2f}元")
# 3. 高于日均的日期
above_avg = revenue > daily_avg
days = np.array(['周一', '周二', '周三', '周四', '周五', '周六', '周日'])
print(f"高于日均的日子: {days[above_avg]}")
# 4. 月度预估
monthly = daily_avg * 30
print(f"月度预估: {monthly:.2f}元")
练习3:广播机制应用
题目:有一个 5 行 3 列的成绩矩阵(5个学生,3门课),请使用广播完成:
- 将每个学生的成绩减去该学生的平均分(学生维度的中心化)
- 将每门课的成绩除以该门课的最高分(归一化到 0~1)
参考答案:
scores = np.array([[85, 90, 78],
[92, 88, 95],
[76, 82, 88],
[95, 91, 89],
[70, 75, 80]])
# 1. 减去每个学生自己的平均分(按行求均值,保持维度)
row_mean = scores.mean(axis=1, keepdims=True)
centered = scores - row_mean
print("学生维度中心化:")
print(np.round(centered, 1))
# 2. 归一化到 0~1(每列除以该列最大值)
col_max = scores.max(axis=0)
normalized = scores / col_max
print("\n归一化 (0~1):")
print(np.round(normalized, 3))
练习4:向量化思维训练
题目:不使用任何 for 循环,用 NumPy 计算 1~1000 中所有偶数的平方和。
参考答案:
# 方法:先筛选偶数,再平方,再求和
numbers = np.arange(1, 1001)
even_numbers = numbers[numbers % 2 == 0]
result = (even_numbers ** 2).sum()
print(f"1~1000中所有偶数的平方和: {result}")
# 一行写法
print(np.sum(np.arange(2, 1001, 2) ** 2))
本节总结
本节我们深入学习了 NumPy 的核心知识。重点回顾:
- ndarray 是 NumPy 的核心:理解
ndim、shape、dtype、size四个属性是掌握 NumPy 的第一步。 - 数组创建方式多样:从列表转换、内置函数(zeros/ones/arange/linspace)、随机生成,根据场景灵活选择。
- 数据类型统一且紧凑:通过
dtype和astype()控制数据类型,在精度和内存之间做权衡。 - 向量化运算替代循环:NumPy 的算术运算都是逐元素进行的,不需要写 for 循环。
- 广播是高级运算的关键:理解广播规则后,可以轻松实现复杂的矩阵操作,代码简洁高效。
- 聚合运算配合 axis:
axis=0按列、axis=1按行,这是处理多维数据的基本功。
记住一句话:在 NumPy 的世界里,没有循环,只有数组。这是从"程序员思维"到"数据分析思维"的重要转变。
到此这篇关于深入解析NumPy中数组的核心概念与操作技巧的文章就介绍到这了,更多相关NumPy数组操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Python3.6通过自带的urllib通过get或post方法请求url的实例
下面小编就为大家分享一篇Python3.6通过自带的urllib通过get或post方法请求url的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2018-05-05
Python使用Marshmallow轻松实现序列化和反序列化
这篇文章主要为大家详细介绍了Python如何使用Marshmallow轻松实现序列化和反序列化,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下2025-03-03


最新评论