第41课:Python|数据分析入门【Numpy数组运算与底层原理精讲】

在这里插入图片描述

文章目录


📖 开篇导读

在之前的课程中,我们学习了Python的基础语法、文件操作、数据库操作以及办公自动化。这些技能已经能够处理日常的大部分编程任务。但是,当遇到大量数值计算、科学计算或数据分析时,Python原生的列表和循环往往会显得力不从心——性能低下,代码冗长。

这时,我们需要一个强大的数值计算库:NumPy(Numerical Python)。NumPy是Python科学计算的基石,它提供了高性能的多维数组对象(n维数组,ndarray)以及丰富的数组运算函数。Pandas、SciPy、Scikit-learn、TensorFlow、PyTorch等数据科学和机器学习库都建立在NumPy的基础之上。

💡 工作场景

  • 数据科学:数据清洗、特征工程、统计分析。
  • 机器学习:处理特征矩阵、标签向量。
  • 图像处理:图像本质就是三维数组(高度、宽度、通道)。
  • 信号处理:音频、金融时间序列数据。

本课将系统学习NumPy的核心功能:

  • ndarray数组的创建、属性、数据类型
  • 索引、切片、布尔索引、花式索引
  • 数组运算:向量化操作、广播机制
  • 通用函数(ufunc)、聚合函数
  • 线性代数运算
  • 数组的存储与加载
  • 底层原理:内存布局、视图与副本

学完本课,你将掌握NumPy的基本使用,为后续学习Pandas、Matplotlib打下坚实的基础。


🎯 学习目标

目标编号具体掌握内容对应面试/工作价值
1️⃣理解NumPy的ndarray与Python列表的区别性能优化意识
2️⃣掌握创建数组的各种方法(array, zeros, ones, arange, linspace, random)数据生成
3️⃣熟练使用索引、切片、布尔索引、花式索引获取数据灵活访问子集
4️⃣理解向量化运算和广播机制,写出高效代码避免Python循环
5️⃣掌握通用函数(ufunc)进行元素级运算快速数学计算
6️⃣了解数组的内存布局(C-order/F-order)、视图与副本的区别底层优化

🔥 面试考点:“NumPy数组和Python列表的区别?”“什么是广播机制?”“如何理解视图(View)和副本(Copy)?”“axis参数的含义?”


📚 知识点理论精讲

一、NumPy简介与安装

1.1 NumPy是什么?

NumPy是一个开源的Python科学计算库,主要提供了:

  • 高性能的ndarray(n维数组)对象。
  • 快速向量化运算,避免Python循环。
  • 丰富的数学函数(三角函数、指数、对数等)。
  • 线性代数、傅里叶变换、随机数生成等功能。

1.2 安装

pip install numpy

在Jupyter Notebook或Python脚本中导入:

import numpy as np        # 约定俗成的别名

二、ndarray:多维数组对象

2.1 ndarray与Python列表的区别

特性Python列表NumPy ndarray
元素类型可以任意类型混搭所有元素类型相同
内存占用较大(每个元素是独立的Python对象)连续内存,紧凑
运算速度慢(循环逐个处理)快(向量化,C语言实现)
功能基础序列操作丰富数学、统计、线性代数函数
# 示例对比
lst = [1, 2, 3]
arr = np.array([1, 2, 3])
print(lst * 2)      # [1,2,3,1,2,3] 重复列表
print(arr * 2)      # [2,4,6] 元素逐个乘以2

2.2 创建数组

从列表创建
import numpy as np

# 一维
arr1 = np.array([1, 2, 3])

# 二维
arr2 = np.array([[1,2,3], [4,5,6]])

# 指定数据类型
arr3 = np.array([1,2,3], dtype=np.float32)   # dtype: int32, float64, bool等
常用创建函数
# 全零数组
zeros = np.zeros((3,4))          # 3行4列全0

# 全一数组
ones = np.ones((2,3))

# 单位矩阵
eye = np.eye(5)                  # 5x5单位矩阵

# 未初始化(内容随机)
empty = np.empty((2,2))

# 等差数列
arange = np.arange(0, 10, 2)     # [0,2,4,6,8]

# 等间隔点
linspace = np.linspace(0, 1, 5)  # [0, 0.25, 0.5, 0.75, 1]

# 随机数
random_arr = np.random.rand(3,3)         # 均匀分布[0,1)
randn_arr = np.random.randn(3,3)         # 标准正态分布
randint_arr = np.random.randint(0, 10, size=(2,3))  # 整数

2.3 数组属性

arr = np.array([[1,2,3], [4,5,6]])
print(arr.shape)      # (2,3) 维度大小
print(arr.ndim)       # 2 维度数
print(arr.size)       # 6 元素总数
print(arr.dtype)      # int64 元素类型
print(arr.itemsize)   # 8 每个元素字节数
print(arr.nbytes)     # 48 总字节数

2.4 数据类型

NumPy支持的数据类型比Python更丰富,常用:

类型描述示例
int8, int16, int32, int64有符号整数dtype=np.int32
uint8, uint16, uint32, uint64无符号整数常用于图像(0-255)
float16, float32, float64浮点数dtype=np.float64
bool布尔dtype=bool
complex64, complex128复数dtype=np.complex128

三、索引与切片

3.1 基础索引

与Python列表类似,但多维数组支持多轴索引。

arr = np.array([[1,2,3], [4,5,6], [7,8,9]])
print(arr[0, 1])        # 2 (第0行,第1列)
print(arr[1])           # [4,5,6] (第1行)
print(arr[1][2])        # 6 (等价于 arr[1,2])

3.2 切片

切片语法:[start:stop:step, start:stop:step]

print(arr[0:2, 1:3])    # 第0-1行,第1-2列 -> [[2,3],[5,6]]
print(arr[:, 1])        # 所有行的第1列 -> [2,5,8]
print(arr[::2, ::2])    # 隔行隔列 -> [[1,3],[7,9]]

注意:切片返回的是原数组的视图(view),不是副本。修改视图会影响原数组。

sub = arr[0:2, 0:2]
sub[0,0] = 99
print(arr[0,0])         # 99 (原数组也被修改)

3.3 布尔索引

使用布尔数组筛选元素。

arr = np.array([1, 2, 3, 4, 5])
mask = arr > 3
print(mask)             # [False, False, False, True, True]
print(arr[mask])        # [4,5]

# 直接在条件中
print(arr[arr > 3])     # [4,5]

# 二维示例
arr2d = np.array([[1,2],[3,4],[5,6]])
print(arr2d[arr2d[:,1] > 3])  # 第2列大于3的行 -> [[3,4],[5,6]]

3.4 花式索引(Fancy Indexing)

使用整数数组作为索引。

arr = np.array([10, 20, 30, 40, 50])
indices = [1, 3, 4]
print(arr[indices])     # [20,40,50]

# 二维
arr2d = np.arange(12).reshape(3,4)
# 使用两个列表,分别指定行和列
print(arr2d[[0,2], [1,2]])   # (0,1)和(2,2)位置的元素 -> [1,10]
# 如果想取多行多列的子集,可以用np.ix_
rows = np.array([0,2])
cols = np.array([1,2])
print(arr2d[np.ix_(rows, cols)])  # 得到2x2矩阵

四、数组运算——向量化

向量化是指对数组元素进行批量操作,无需显式循环。

4.1 算术运算

a = np.array([1,2,3])
b = np.array([4,5,6])
print(a + b)          # [5,7,9]
print(a * b)          # [4,10,18]
print(a ** 2)         # [1,4,9]
print(np.sqrt(a))     # [1, 1.414, 1.732]

4.2 标量运算

print(a + 10)         # [11,12,13]
print(a * 2)          # [2,4,6]

4.3 矩阵乘法(点积)

A = np.array([[1,2],[3,4]])
B = np.array([[5,6],[7,8]])
print(np.dot(A, B))   # 矩阵乘法
# 或使用 @ 运算符 (Python 3.5+)
print(A @ B)

五、广播机制(Broadcasting)

当两个数组形状不同时,NumPy会尝试自动广播,使它们能够进行运算。

5.1 广播规则

  1. 从尾部维度对齐。
  2. 两个数组的某个维度长度相同,或其中一个长度为1,或其中一个缺失。
  3. 如果不满足,抛出ValueError
# 例1: 一维数组广播到二维数组
arr = np.ones((3,4))
vec = np.array([1,2,3,4])
result = arr + vec     # vec形状(4,)扩展为(3,4)
print(result.shape)    # (3,4)

# 例2: 列向量广播
col = np.array([[1],[2],[3]])   # (3,1)
row = np.array([10,20,30])      # (3,)
result = col + row               # 自动广播到(3,3)

5.2 手动添加维度

使用np.newaxisreshape

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

六、通用函数(ufunc)

通用函数是对数组中每个元素进行快速运算的函数。

6.1 一元ufunc

arr = np.array([-1, 0, 1, 2])
np.abs(arr)          # 绝对值
np.sqrt(arr)         # 平方根
np.exp(arr)          # 指数 e^x
np.log(arr)          # 自然对数
np.sin(arr)          # 三角函数

6.2 二元ufunc

a = np.array([1,2,3])
b = np.array([4,5,6])
np.add(a, b)         # 等价于 a+b
np.maximum(a, b)     # 逐元素取最大值
np.minimum(a, b)     # 逐元素取最小值

6.3 聚合函数

arr = np.array([[1,2],[3,4]])
print(np.sum(arr))          # 10
print(np.mean(arr))         # 2.5
print(np.std(arr))          # 标准差
print(np.min(arr))          # 1
print(np.max(arr))          # 4

# 指定轴 axis: 0列方向,1行方向
print(np.sum(arr, axis=0))  # [4,6] 每列和
print(np.sum(arr, axis=1))  # [3,7] 每行和

七、形状操作

7.1 reshape与resize

arr = np.arange(12)
# reshape返回新数组,不修改原数组
reshaped = arr.reshape(3,4)
# resize修改原数组(或返回None)
arr.resize(3,4)

7.2 展平(ravel vs flatten)

arr = np.array([[1,2],[3,4]])
# ravel 返回视图(如果可能)
flat_view = arr.ravel()
# flatten 返回副本
flat_copy = arr.flatten()

7.3 拼接与分裂

# 垂直拼接
a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
vstack = np.vstack((a, b))      # (4,2)

# 水平拼接
hstack = np.hstack((a, b))      # (2,4)

# 合并通过轴
concat = np.concatenate((a, b), axis=0)

# 分裂
arr = np.arange(12).reshape(3,4)
split = np.split(arr, 3, axis=0)   # 分成三个子数组

八、内存布局、视图与副本

8.1 内存布局

NumPy数组存储在连续的内存块中,有两种顺序:

  • C-order(行优先):默认,最后轴变化最快。
  • F-order(列优先):Fortran风格。
arr = np.arange(12).reshape(3,4)
print(arr.flags['C_CONTIGUOUS'])   # True
arr_f = np.array(arr, order='F')

8.2 视图(View)与副本(Copy)

  • 视图:共享数据,修改视图影响原数组。切片、ravel()(通常)返回视图。
  • 副本:独立数据,修改副本不影响原数组。copy()flatten()返回副本。
arr = np.arange(10)
view = arr[::2]         # 视图
copy = arr.copy()       # 副本
view[0] = 999
print(arr[0])           # 999 被修改

九、线性代数

NumPy提供了linalg子模块。

A = np.array([[1,2],[3,4]])
# 矩阵的逆
inv = np.linalg.inv(A)
# 行列式
det = np.linalg.det(A)
# 特征值、特征向量
eigvals, eigvecs = np.linalg.eig(A)
# 解线性方程组 Ax = b
b = np.array([5,6])
x = np.linalg.solve(A, b)

十、随机数生成

# 设置随机种子保证可重复性
np.random.seed(42)

# 均匀分布[0,1)
rand = np.random.rand(3,3)

# 标准正态分布
randn = np.random.randn(3,3)

# 整数随机
randint = np.random.randint(0, 10, size=(3,3))

# 随机排列
arr = np.arange(10)
np.random.shuffle(arr)        # 原地打乱
perm = np.random.permutation(10)  # 返回新数组

# 随机选择
choice = np.random.choice([1,2,3], size=5, p=[0.1,0.6,0.3])  # 带权重

十一、数据存储与加载

11.1 文本文件

# 保存为一维或二维txt
arr = np.arange(12).reshape(3,4)
np.savetxt('data.txt', arr, fmt='%d', delimiter=',')

# 加载
loaded = np.loadtxt('data.txt', delimiter=',')

11.2 二进制文件(.npy)

np.save('arr.npy', arr)
loaded = np.load('arr.npy')

# 多个数组压缩保存
np.savez('multiple.npz', a=arr, b=arr*2)
data = np.load('multiple.npz')
print(data['a'])

💻 代码案例实操

案例1:基础创建与属性

"""
array_basics.py
创建数组并查看属性
"""

import numpy as np

# 从列表创建
arr1 = np.array([1, 2, 3, 4, 5])
print("一维数组:", arr1)
print("shape:", arr1.shape, "ndim:", arr1.ndim)

# 二维
arr2 = np.array([[1, 2], [3, 4], [5, 6]])
print("二维数组:\n", arr2)
print("shape:", arr2.shape)

# 常用创建方法
zeros = np.zeros((2, 3))
ones = np.ones((2, 3))
eye = np.eye(4)
range_arr = np.arange(0, 10, 2)
lin = np.linspace(0, 1, 5)

print("zeros:\n", zeros)
print("arange:", range_arr)

案例2:索引与切片

"""
indexing_slicing.py
演示各种索引方式
"""

import numpy as np

arr = np.arange(1, 13).reshape(3, 4)
print("原始数组:\n", arr)

# 基础索引
print("第1行第2列:", arr[0, 1])

# 切片
print("前2行,第2-4列:\n", arr[0:2, 1:4])
print("所有行第2列:", arr[:, 1])

# 布尔索引
mask = arr > 6
print("大于6的元素:", arr[mask])

# 花式索引
rows = [0, 2]
cols = [1, 3]
print("指定位置:", arr[rows, cols])           # 得到 [arr[0,1], arr[2,3]]
# 想要子矩阵使用 np.ix_
print("子矩阵:\n", arr[np.ix_(rows, cols)])

案例3:向量化运算与广播

"""
vectorization_broadcast.py
演示数组运算的高效性和广播机制
"""

import numpy as np
import time

# 对比列表与数组的速度
def list_square(n):
    lst = list(range(n))
    result = [x**2 for x in lst]
    return result

def array_square(n):
    arr = np.arange(n)
    result = arr**2
    return result

n = 10_000_000
start = time.time()
list_square(n)
print(f"列表推导耗时: {time.time()-start:.4f}s")

start = time.time()
array_square(n)
print(f"NumPy向量化耗时: {time.time()-start:.4f}s")

# 广播示例
a = np.array([[1,2,3],[4,5,6]])
b = np.array([10,20,30])
print("a + b 广播结果:\n", a + b)

c = np.array([[1],[2]])
d = np.array([10,20,30])
print("c + d 广播结果:\n", c + d)

案例4:通用函数与聚合

"""
ufunc_aggregation.py
演示ufunc和聚合函数,以及axis参数
"""

import numpy as np

arr = np.random.randn(3, 4)   # 3行4列正态分布随机数
print("原始数组:\n", arr)

# 一元ufunc
print("绝对值前3个元素:\n", np.abs(arr[0:1]))

# 聚合函数
print("总和:", np.sum(arr))
print("均值:", np.mean(arr))
print("按行求和:", np.sum(arr, axis=1))
print("按列求均值:", np.mean(arr, axis=0))

# 累积运算
print("累积和:", np.cumsum(arr, axis=1))
print("累积乘积:", np.cumprod(arr, axis=1))

案例5:形状操作

"""
shape_manipulation.py
演示reshape, ravel, flatten, concatenate
"""

import numpy as np

arr = np.arange(12)
print("原始:", arr)

# reshape
reshaped = arr.reshape(3, 4)
print("reshape(3,4):\n", reshaped)

# ravel (视图)
flat = reshaped.ravel()
flat[0] = 99
print("原数组被修改:\n", reshaped)

# flatten (副本)
flattened = reshaped.flatten()
flattened[0] = 88
print("原数组不变:\n", reshaped)

# 拼接
a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
v = np.vstack((a,b))
h = np.hstack((a,b))
print("垂直拼接:\n", v)
print("水平拼接:\n", h)

# 分割
split = np.split(reshaped, 3, axis=0)
print("分割为3部分:\n", split)

案例6:线性代数求解方程组

"""
linear_algebra.py
使用NumPy解线性方程组
"""

import numpy as np

# 方程组:
# 2x + y = 5
# x + 3y = 6
A = np.array([[2, 1], [1, 3]])
b = np.array([5, 6])

# 方法1: 求逆
x_inv = np.linalg.inv(A) @ b
print("逆矩阵法解:", x_inv)

# 方法2: solve
x_solve = np.linalg.solve(A, b)
print("solve法解:", x_solve)

# 验证
print("Ax:", A @ x_solve)

案例7:随机数模拟掷骰子

"""
dice_simulation.py
使用随机数模拟掷骰子并统计频率
"""

import numpy as np

np.random.seed(42)
n = 100000
dice = np.random.randint(1, 7, size=n)

# 统计各面次数
values, counts = np.unique(dice, return_counts=True)
print("点数:", values)
print("频率:", counts / n)

# 期望值约1/6 ≈ 0.16667
print("理论概率:", 1/6)

案例8:图像处理简单示例(数组操作)

"""
image_demo.py
模拟图像处理(二维灰度图)
"""

import numpy as np

# 模拟一个灰度图像(8x8,像素0-255)
img = np.random.randint(0, 256, size=(8,8), dtype=np.uint8)
print("原始图像:\n", img)

# 翻转(上下)
img_flipud = np.flipud(img)
print("上下翻转:\n", img_flipud)

# 亮度增强(乘以1.5,截断到255)
bright = np.clip(img * 1.5, 0, 255).astype(np.uint8)
print("亮度增强:\n", bright)

# 二值化(大于128为255,否则0)
binary = np.where(img > 128, 255, 0).astype(np.uint8)
print("二值化:\n", binary)

⚠️ 易错点避坑总结

序号坑点描述后果解决方案
1误以为切片返回副本修改切片导致原数组改变需要副本时使用.copy()
2不同数据类型的数组运算导致类型提升结果精度可能超出预期明确指定dtype或使用.astype()
3广播规则理解错误维度不匹配导致ValueError检查形状,使用np.newaxis调整
4np.arangenp.linspace混淆步长参数理解偏差记住arange是步长,linspace是点数
5花式索引与切片混淆花式索引返回副本,不是视图注意性能影响
6axis参数方向错误对行求和/列求和混淆记住axis=0是跨行(垂直),axis=1是跨列(水平)
7使用reshape时元素总数必须匹配ValueError确保新形状元素总数与原数组相同
8随机数未设种子导致结果不可重现调试困难测试时设置np.random.seed()
9忘记导入numpy或使用别名npNameError习惯import numpy as np
10在循环中逐个操作NumPy数组元素失去向量化优势,速度极慢改用向量化运算或ufunc

📝 课后实战练习题

第1题:数组创建与运算

创建一个3x3的矩阵,元素为1-9,将其形状改为1x9,然后计算每个元素的平方根,再求和。

第2题:布尔索引筛选

生成一个包含100个随机整数的数组(范围0-100),筛选出所有大于60的元素,并计算其平均值。

第3题:矩阵乘法

定义两个矩阵A(2x3)和B(3x2),使用np.dot@计算乘积,并验证形状。

第4题:使用广播实现归一化

有一个二维数组,形状(10,4),对每一列进行标准化:(列元素 - 列均值) / 列标准差。不使用循环。

第5题:统计考试成绩

给定一个5x3的成绩矩阵(5名学生,3门课),计算每个学生的总分、平均分,并找出总分最高的学生的索引。

第6题:正弦波信号生成

生成一个从0到4π,共1000个点的等间距序列,计算其正弦值,并找出正弦值大于0.5的点的数量。

第7题:图像处理(数组切片)

创建一个15x15的灰度图像(随机整数0-255),将中心5x5区域设置为0(黑色),四周设置为255(白色),并显示(用matplotlib,可选)。

第8题:解线性方程组

使用NumPy求解以下方程组:

3x + y = 9
x + 2y = 8

输出x和y的值。


🧠 知识点思维导图总结

第41课:NumPy入门

ndarray

创建: array, zeros, ones, arange, linspace

属性: shape, ndim, dtype, size

数据类型: int, float, bool

索引切片

基础索引

切片

布尔索引

花式索引

向量化运算

算术运算

矩阵乘法 @ / dot

广播机制

通用函数 ufunc

一元: abs, sqrt, exp, sin

二元: add, maximum

聚合: sum, mean, axis

形状操作

reshape, ravel, flatten

拼接: vstack, hstack, concatenate

分裂: split

视图与副本

视图: 切片, ravel

副本: copy, flatten

随机数

seed, rand, randn, randint, shuffle

线性代数

inv, det, eig, solve

IO

save, load, savetxt, loadtxt

面试考点

列表vs数组

广播原理

视图与副本

axis含义


🔜 下节课预告

NumPy是数据科学的基石。下一节课我们将学习Pandas,它是基于NumPy构建的数据分析库,提供了DataFrame等高级数据结构,让数据处理更加便捷。

第42课:Pandas核心用法:DataFrame、数据清洗与统计分析实战

内容包括:

  • Series与DataFrame的创建和操作
  • 数据的读取与写入(CSV、Excel)
  • 数据清洗:缺失值处理、重复值删除、数据类型转换
  • 数据筛选、排序、分组聚合
  • 数据合并与连接(merge、concat)
  • 实战:电影评分数据分析

Pandas是数据科学家的必备工具,学完后你将能高效处理表格数据。

🌟 学习鼓励:NumPy是Python数据科学生态的心脏。虽然它的语法相对简单,但理解向量化思维广播是提高代码性能的关键。请多动手练习,尤其是对比Python原生循环和NumPy向量化的性能差异,你会对NumPy的强大有更深刻的体会。下一节课我们将进入Pandas,让数据分析变得更简单!


🔗《50节课 Python 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值