本节摘要:真实数据很少一开始就长得顺眼:要么维度不对,要么零散在多个数组里。本节解决"摆形状"和"拼数据"两件事——reshape 重塑、转置、拼接、分割,并用 axis 参数讲透聚合统计的维度含义,最后用电商订单金额完成一次综合统计实战。
阅读完本节,你应当能够:
数据处理里有两类高频需求,一是"形状不对",二是"数据太散"。形状不对:从数据库拉回来的数据可能是扁平的一维数组,但要按 7 天一周分组看趋势就得摆成二维。数据太散:上个月的订单金额和这个月的分别存在两个数组里,想算季度总额就得先拼起来。本节把这两类操作一次讲清。
形状操作是"摆数据"的艺术,四类操作对应四种需求:
reshape 不改变数据本身,只改变它的形状元数据。只要元素总数不变,任何形状都能互相转换。注意 reshape 通常返回视图(共享数据),修改结果会改动原数组。
import numpy as np arr = np.arange(12) # [0..11] m = arr.reshape(3, 4) # 3 行 4 列 print(m)
[[ 0 1 2 3] [ 4 5 6 7] [ 8 9 10 11]]
💡 关键直觉:reshape 是按"C 顺序"填充的——先填满一行再换行。写
-1可以让 NumPy 自动推断该维大小,arr.reshape(-1, 1)常用来把一维数组变成一列。
m.T # 转置,3x4 变 4x3 m.flatten() # 展开成一维,返回副本 m.ravel() # 展开成一维,返回视图(尽量用 flatten 更安全)
a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) np.concatenate([a, b]) # 一维拼接: [1 2 3 4 5 6] np.vstack([a, b]) # 纵向堆叠: 2 行 3 列 np.hstack([a, b]) # 横向拼接: 1 行 6 列 # 分割 big = np.arange(12).reshape(3, 4) np.vsplit(big, 3) # 按行切成 3 块 np.hsplit(big, 2) # 按列切成 2 块
sum、mean、std 等聚合函数都能接受 axis 参数,指定沿着哪个维度"压扁":
axis=0:沿第 0 维(行方向)压,得到每一列的统计值axis=1:沿第 1 维(列方向)压,得到每一行的统计值# 3 家门店 4 天的销售额(单位:千元) sales = np.array([ [12.0, 15.0, 14.0, 18.0], # 门店 A [8.0, 9.0, 11.0, 10.0], # 门店 B [20.0, 22.0, 19.0, 25.0], # 门店 C ]) sales.sum() # 全部销售额 sales.mean(axis=1) # 每家门店的日均销售额 sales.sum(axis=0) # 每天的总销售额 sales.max(axis=0) # 每天卖得最好的门店业绩
结果含义要读对:sales.mean(axis=1) 返回 3 个值,分别对应门店 A、B、C 的日均——"沿第 1 维压"就是把列求和再除以列数,剩下的是行。
⚠️ 常见坑:axis 方向记反是新手高频错误。一个可靠的口诀——axis 等于几,就把第几维"压没"。
mean(axis=1)后形状从 (3,4) 变成 (3,),第 1 维没了,剩下的是每行的值。
| 场景 | 用哪个聚合 |
|---|---|
| 总和、总额 | sum |
| 平均水平 | mean |
| 波动程度 | std / var |
| 极端值 | max / min / argmax |
| 稳健中心(抗异常值) | median |
| 分布形状 | percentile |
金额类数据通常右偏(少数大单拉高均值),这时中位数比均值更能代表"典型客单价"——这是分析时的关键判断,第 4、5 章还会反复出现。
把电商订单金额数组按维度拆解统计:
import numpy as np np.random.seed(7) # 模拟 5 家门店各 4 天的订单总额(万元) orders = np.random.randint(3, 9, size=(5, 4)).astype(float) print("日流水矩阵:\n", orders) print("全部门店总流水:", orders.sum(), "万元") print("各门店周均:", orders.mean(axis=1).round(2)) print("单日最高流水:", orders.max()) print("流水的中位数:", np.median(orders)) print("前 25% 分位:", np.percentile(orders, 25))
把两批数据拼起来再统一统计,是常见的完整流程:
q1 = np.array([12, 15, 9]) # 一季度各月 q2 = np.array([18, 14, 21]) # 二季度各月 half = np.concatenate([q1, q2]) print(half.sum(), half.mean()) # 半年总额与月均
reshape、ravel 是视图操作,几乎零开销;flatten、拼接、分割涉及复制,数据量大时注意内存。拼接大数组优先用 np.concatenate 一次性拼,避免在循环里反复拼接——后者会不断申请新内存,慢且费内存。
| 操作 | 代码 | 效果 |
|---|---|---|
| 重塑 | arr.reshape(2, 3) |
改形状不改数据 |
| 展平 | arr.flatten() |
变一维(副本) |
| 转置 | arr.T |
行列互换 |
| 纵向堆叠 | np.vstack([a, b]) |
按行拼接 |
| 横向拼接 | np.hstack([a, b]) |
按列拼接 |
| 通用拼接 | np.concatenate([a, b], axis=0) |
指定轴拼接 |
| 按行分割 | np.vsplit(arr, n) |
切 n 份 |
| 按列分割 | np.hsplit(arr, n) |
切 n 份 |
# 把 3 个月的销售数据合并成一张表 jan = np.array([12, 15, 9]) feb = np.array([18, 14, 21]) mar = np.array([16, 17, 11]) # 纵向堆叠:3 行,每行一个月 quarter = np.vstack([jan, feb, mar]) # 横向拼接:1 行,3 个月数据依次排开 whole = np.hstack([jan, feb, mar])
⚠️ 常见坑:vstack 要求各数组列数一致(纵向堆叠是堆行),hstack 要求行数一致。形状不匹配会直接报错——先
print(a.shape, b.shape)核对,再决定用哪个。
同一组数据,不同的统计口径讲不同的故事:
sales = np.array([[12, 15, 14], [8, 9, 11], [20, 22, 19]]) sales.sum() # 总盘子:一共卖了多少 sales.sum(axis=1) # 每个门店的业绩 sales.mean(axis=0) # 每天的平均水平 sales.argmax(axis=0) # 每天谁是冠军门店
argmax(axis=0) 返回每天业绩最好的门店下标,配合门店名单就能回答"每天哪家店最强"。先想清楚业务问题(看总量?看个体?看每天?),再选聚合方式和 axis,这是统计实战的通用心法。
真实任务很少只用单一操作,往往是把本节工具串起来:
# 需求:把 12 个月销售数据按季度汇总,并找出最好的季度 monthly = np.arange(100, 400, 25) # 12 个月的数据 quarterly = monthly.reshape(4, 3).sum(axis=1) # 每 3 个月一组求和 print("季度汇总:", quarterly) print("最好的季度:", quarterly.argmax() + 1) print("季度间波动:", quarterly.std().round(2))
reshape(4, 3) 把 12 个数摆成 4 行 3 列(每行一个季度),sum(axis=1) 沿列压出每季度总和——一次 reshape 加一次聚合,季度汇总就出来了。这就是"形状操作 + 聚合"组合拳的典型用法。
本章的数组操作到 Pandas 里都有对应物:数组 → DataFrame、reshape → 透视、拼接 → concat、聚合 → groupby。先想清 NumPy 里"数据怎么摆、怎么算",学 Pandas 时就只剩换 API——这也是为什么本书把 NumPy 放在 Pandas 之前。
reshape(-1, 1) 自动推断.T,展开用 flatten(副本)更安全mean(axis=1) 得每行均值NumPy 这座地基已经打牢,下一章登上它建起的第一个大房子——Pandas,从此跟表格数据打交道。