Pandas教学25:NumPy 的实战价值集中的四件事

NumPy 的实战价值集中在四件事:批量计算(向量化)、条件筛选(布尔掩码)、广播(形状对齐)、结构变换(拼接/拆分/重塑)。下面 6 个案例分别对应一个真实场景,全部不含绘图。

案例 1:成绩表分析 ------ 吃透 axis

场景:30 名学生、5 门课的成绩矩阵,一次算出每人总分、各科均分、及格率、排名、等级。

python 复制代码
import numpy as np

rng = np.random.default_rng(42)
成绩 = rng.integers(50, 101, size=(30, 5))     # 30行(学生) × 5列(课程)

总分   = 成绩.sum(axis=1)                       # 横向压缩 → 每个学生的总分(长度30)
各科均分 = 成绩.mean(axis=0)                    # 纵向压缩 → 每门课的均分(长度5)
及格率  = (成绩 >= 60).mean(axis=0)             # 布尔数组求均值 = 及格比例
最高分  = 成绩.max(axis=0)

排名 = np.argsort(-总分)[:3]                    # 降序后取前3名,得到的是行号
等级 = np.select(
    [总分 >= 450, 总分 >= 400, 总分 >= 350],
    ["A", "B", "C"],
    default="D"
)

axis 的记忆法:axis 指"被消掉的那个维度"。

text 复制代码
             科目1  科目2  科目3  科目4  科目5
学生1  [      90     85     77     92     88 ]  ← axis=1 沿行压缩:这一行变成一个总分
学生2  [      60     72     68     81     55 ]
   ↑
 axis=0 沿列压缩:这一列变成一个均分

所以 axis=1 出"每人一行一个数",axis=0 出"每科一个数"。布尔数组 (成绩 >= 60) 的 .mean() 直接就是及格率,这个技巧在统计里高频使用。

案例 2:数据清洗 ------ 缺失值 + 异常值

场景:一份体检数据里混着 NaN 和明显离谱的数值。

python 复制代码
原始 = np.array([88, np.nan, 92, 120, 75, np.nan, 300, 81, 95, 70], dtype=float)

有效  = ~np.isnan(原始)                 # 布尔掩码(不能写 原始 != np.nan)
中位数 = np.nanmedian(原始)             # 90.0,跳过 NaN 计算

填补 = np.where(np.isnan(原始), 中位数, 原始)   # NaN 换成中位数

Q1, Q3 = np.percentile(填补, [25, 75])          # 82.75 / 94.25
IQR = Q3 - Q1                                   # 11.5
下界, 上界 = Q1 - 1.5 * IQR, Q3 + 1.5 * IQR      # 65.5 / 111.5

清洗后 = np.clip(填补, 下界, 上界)               # 300 和 120 被压到 111.5

三个要点:

  • np.nanmedian / np.nanmean / np.nansum 会自动忽略 NaN,普通 mean 会得到 NaN;
  • np.where(条件, 真值, 假值) 是三目运算的向量化版本,一次替换整组;
  • np.clip 是"截断式"异常值处理,不删除样本,只把极端值压回边界。

案例 3:销售时间序列 ------ 移动平均、环比、累计

场景:14 天日销量,做趋势平滑与累计业绩统计。

python 复制代码
日销量 = np.array([120, 135, 128, 160, 172, 155, 168,
                   180, 175, 190, 205, 198, 210, 225])

七日移动平均 = np.convolve(日销量, np.ones(7) / 7, mode="valid")  # 长度 14-7+1 = 8
日环比   = np.diff(日销量)                 # 后一天 - 前一天,长度13
累计销量 = np.cumsum(日销量)               # 逐日累加
周环比   = 日销量[7:] / 日销量[:7] - 1     # 第二周 vs 第一周(同位置对比)

np.convolve 做的是"滑动窗口加权求和",卷积核取 np.ones(7)/7 就是 7 日简单移动平均;mode="valid" 表示只保留窗口完全落在数据内的部分,因此结果比原数组短 6 个。

案例 4:距离矩阵 ------ 广播的经典用法

场景:3 个客户要各自找最近的线下门店,店有 4 家。

python 复制代码
门店 = np.array([[0, 0], [10, 0], [0, 15], [12, 18]])   # (4, 2)
客户 = np.array([[3, 4], [9, 1], [5, 12]])              # (3, 2)

# 广播:客户插一个轴,门店插一个轴,自动扩展成 (3, 4, 2) 的差值张量
差   = 客户[:, None, :] - 门店[None, :, :]
距离 = np.sqrt((差 ** 2).sum(axis=-1))                  # (3, 4)

最近门店 = 距离.argmin(axis=1)      # [0 1 2]
最近距离 = 距离.min(axis=1)         # [5.   1.41 5.83]
text 复制代码
客户(3,2) → 客户[:, None, :] 变成 (3, 1, 2)
门店(4,2) → 门店[None, :, :] 变成 (1, 4, 2)
相减广播 → (3, 4, 2)   即 每个客户 × 每家店 × 二维坐标

广播规则一句话:两个形状从右往左逐位比较,相等或其中一个是 1 就能对齐,否则报错。这段代码如果写成双重循环,速度会差两个数量级。

案例 5:表格结构操作 ------ 拼接、拆分、重塑、排序

场景:两个季度的销售记录要合并、加编号列、按月份重组、按业绩排序。

python 复制代码
一季度 = np.array([[101, 88], [102, 92], [103, 79]])
二季度 = np.array([[201, 95], [202, 85]])

全量 = np.vstack([一季度, 二季度])          # 纵向堆叠:追加记录(行)
编号列 = np.arange(len(全量)).reshape(-1, 1)  # 变成 (5,1) 的列向量
带编号 = np.hstack([编号列, 全量])            # 横向拼接:追加字段(列)

前三条, 剩余 = np.split(全量, [3])            # 在索引3处切一刀,切成两段

月份表 = np.arange(1, 13).reshape(3, 4)       # 3个季度 × 4个月
季度合计 = 月份表.sum(axis=1)                 # 每行求和 → 3个季度
月度合计 = 月份表.sum(axis=0)                 # 每列求和 → 4个月

按业绩排序 = 全量[np.argsort(-全量[:, 1])]     # 取第2列作为排序键

对照:vstack 竖着拼(加行)、hstack 横着拼(加列);np.split(数组, [切点]) 按位置切;reshape(-1, n) 里的 -1 表示"这一维自动推算"。

案例 6:向量化实战 ------ 批量折扣 + 蒙特卡洛估算 π

6.1 会员折扣 (np.where 嵌套实现多级判断):

python 复制代码
金额 = np.array([88, 268, 520, 999, 45, 180])

折扣 = np.where(金额 >= 500, 0.8,
        np.where(金额 >= 200, 0.9, 1.0))     # [1.  0.9 0.8 0.8 1.  1. ]
应付 = np.round(金额 * 折扣, 2)              # [ 88.  241.2 416.  799.2  45.  180. ]

6.2 蒙特卡洛估算 π(100 万次模拟,不用任何循环):

python 复制代码
rng = np.random.default_rng(2024)
n = 1_000_000
x, y = rng.random(n), rng.random(n)

在圆内 = (x ** 2 + y ** 2) <= 1
pi 估计 = 4 * 在圆内.mean()                   # ≈ 3.14

6.3 向量化 vs 循环的真实差距:

python 复制代码
# 慢:逐元素循环
结果 = [金额[i] * 折扣[i] for i in range(len(金额))]

# 快:整数组一次算完(推荐)
结果 = 金额 * 折扣

100 万元素规模下,向量化通常比 Python 循环快 50~200 倍,因为循环在 C 层完成,且省掉了每个元素的解释器开销。


六个高频陷阱

陷阱 错误写法 正确做法
NaN 判断 arr == np.nan 永远 False np.isnan(arr)
切片是视图 sub = arr[:3]; sub[0] = 0 会改原数组 需要独立副本时 .copy()
axis 方向 凭感觉猜 0/1 记住"axis 是会被消掉的那一维"
整数溢出 大数组 .sum() 在 int32 上溢出 指定 dtype=np.int64 或先转 float
随机种子 np.random.seed(42) 旧接口 np.random.default_rng(42) 新接口
reshape 失败 元素总数对不上 元素总数必须相等,-1 只能出现一次
相关推荐
gCode Teacher 格码致知3 小时前
Pandas教学24:NumPy、Pandas、Matplotlib三者之间的关系
numpy·pandas·matplotlib
2601_966949654 小时前
为什么不应该把 QuantDash API Key 直接写进公开代码仓库?从 Git 提交历史理解凭证泄露风险
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
quantdash_cc1 天前
如何检查滚动收益率计算中的窗口长度不足?——从数学定义到 Pandas 实现
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
BK贝壳2 天前
亿级用户签到位图选型复盘:用 bool-hybrid-array 扛冷启动打卡,也把它和 Roaring/Redis 的边界测清楚了
数据结构·python·性能优化·numpy
2601_966949652 天前
如何根据研究的起止时间截取分钟级行情?Python 实战与 QuantDash 数据处理指南
开发语言·python·pandas·量化交易·1024程序员节·股票数据·quantdash
Patrick在香港2 天前
一个加 1 的动作,把最大值变成了最小值
python·numpy·数据类型·整数溢出·数据精度
benchmark_cc2 天前
如何为 QuantDash Python SDK 编写一个最小化的连接验证脚本?
python·数据分析·pandas·量化交易·股票数据·quantdash
万粉变现经纪人2 天前
如何解决 pip install 安装报错 检测到 yanked 版本需更换可用版本 问题
人工智能·python·scrapy·numpy·pandas·pyqt·pip
BK贝壳3 天前
bool-hybrid-array 落盘我只认 .bha:用 Create_BHA / Ask_BHA 做布尔数组序列化
数据结构·python·numpy·1024程序员节