经常听到的透视表是干什么的?

先说结论:透视表 不是一种新的计算方法,它是一个把明细数据按你的要求重新排列的工具。

拿一张两万行的订单明细举例。

每一行是一个订单,你想知道华东卖了多少,就得把华东那几千行金额加起来;想知道九个城市各卖多少,就得分成九堆分别加;想知道每个城市每个品类各卖多少,就得分二十几堆。

这些"分堆再加总"的动作,Excel 用 SUMIF 能做,SQL 用 GROUP BY 能做,pandas 用 groupby 也能做。

但它们有个共同的麻烦:换一种看法,就得重写一遍。

透视表把这件麻烦事变成了填四个格子。你告诉它"按哪个字段分堆""把哪个字段摆到横向""算哪个数字""怎么算",它就把表重新排一遍,随时可以换一种排法。

一句话说清:透视表 = 分组 + 聚合 + 重排,而且改主意的成本几乎为零。

pandas 里对应的函数叫 pivot_table。

1. 先认识四个区域

这是唯一需要提前记住的东西。所有透视操作,本质都是往四个格子里放字段:

区域 参数 作用 填什么
行 index 纵向分组,一行一组 大区、城市
列 columns 横向展开,一列一组 品类、渠道
值 values 要计算的数字 销售额
算法 aggfunc 这个数字怎么算 sum 求和、count 计数、mean 平均

另外两个常用参数:margins=True 加一行合计,fill_value=0 把空位补成 0。

记住这张表,后面的例子全是在往这四个位置里填不同的字段,没有别的新东西。

2. 准备一份明细数据

透视表的原料必须是明细表:一行一条记录,一列一个字段,表头只有一行,不合并单元格。

这个前提不满足,后面全做不下去------合并单元格的报表样式数据就是典型的反例。

字段还得分成两类:

  • 日期、大区、城市、门店、品类、渠道是维度,用来分组;
  • 数量、销售额、成本是度量,用来计算。

透视表干的事,说到底就是把维度放进行或列,把度量放进值。

脚本用固定种子造了两万条订单,覆盖 2026 年 1 到 3 月,九个城市、三个品类、线上线下两个渠道。

python 复制代码
df = build_orders(n=20000, seed=42)
df["毛利额"] = df["销售额"] - df["成本"]

最后这行要注意:毛利额是提前算好放进源表的。

透视表只汇总、不跨列运算,凡是需要几个字段相减相乘才能得到的指标,都得先造出来。

运行数据准备后:

plain 复制代码
========================================================================
准备明细数据
========================================================================
     订单号       日期  大区  城市  品类  渠道   销售额
0  SO000000 2026-01-02  华北  天津  耳机  线下   537.49
1  SO000001 2026-01-16  华南  广州  配件  线下   457.07
2  SO000002 2026-01-17  西南  成都  手机  线下  4111.96
3  SO000003 2026-03-12  华北  北京  手机  线下  3642.10
4  SO000004 2026-02-06  华东  上海  耳机  线上  1795.94

共 20000 行,日期范围 2026-01-01 ~ 2026-03-31

3. 分组汇总:按一个维度加起来

最基础的用法,只填行和值两处:

python 复制代码
pt = df.pivot_table(index="大区", values="销售额", aggfunc="sum")

四行数字,就是把四个大区的销售额分别加起来。到这一步为止,透视表和 groupby 没有区别。

值区域可以一次放多个字段,各用各的算法:

python 复制代码
pt = df.pivot_table(index="大区", values=["销售额", "订单号"],
                    aggfunc={"销售额": "sum", "订单号": "count"})

一个求和,一个计数,两个数字放在一起就能算出客单价:

plain 复制代码
========================================================================
分组汇总:按一个维度加起来
========================================================================
      订单号  销售额  客单价  金额占比
大区
华东    7228  1684.6  2331.0     0.361
华北    4286   997.0  2326.0     0.214
华南    4918  1138.4  2315.0     0.244
西南    3568   846.8  2373.0     0.181
合计   20000  4666.9  2333.0     1.000

华东一家占了三分之一还多,但四个大区的客单价几乎一样,都在 2300 上下。

结论很直接:华东的领先是单量堆出来的,不是客单价高。

只放销售额,你知道谁大谁小;放两个,才知道为什么大。值区域不是只能放一个字段,这点经常被忽略。

4. 多层级:一次看两级

华东占了三分之一,接着自然想知道是哪几个城市撑起来的。index 传一个列表,表就多了一层:

python 复制代码
pt = df.pivot_table(index=["大区", "城市"], values="销售额", aggfunc="sum")
plain 复制代码
========================================================================
多层级:一次看两级
========================================================================
           销售额
大区 城市
华东 上海   743.3
     南京   442.8
     杭州   498.5
华北 北京   726.3
     天津   270.7
华南 广州   581.0
     深圳   557.4
西南 成都   464.6
     重庆   382.2
合计       4666.9

注意 margins 只加了一行总合计,各大区下面没有小计。

列表的顺序就是层级顺序,先大区后城市。上海 743.3 万排第一,北京 726.3 万紧随其后,南京 442.8 万。华东内部,上海一个城市就占了将近一半。

这里有个和 Excel 不一样的地方:margins=True 在多级行标签下只追加一行总合计,不会在华东、华北下面各自生成小计。想让每一级都带小计,得自己再算一次,或者拆成两个透视。

5. 交叉:把维度搬到列上

前面两张表都是纵向的。

如果关心的是"每个城市的品类结构怎么样",纵向的表就不好看了------得把品类从行搬到列:

python 复制代码
pt = df.pivot_table(index="城市", columns="品类",
                    values="销售额", aggfunc="sum", fill_value=0)
plain 复制代码
========================================================================
交叉:把维度搬到列上
========================================================================
各城市品类结构占比(%)
品类  手机  耳机  配件
城市
上海  81.9  15.6   2.4
北京  83.3  14.1   2.6
南京  83.6  14.0   2.3
天津  80.8  16.5   2.8
广州  81.9  15.5   2.6
成都  83.3  14.2   2.5
杭州  82.0  15.3   2.7
深圳  82.6  14.7   2.7
重庆  83.2  14.4   2.3

这一步的意义在于:行和列各放一个维度,两个维度的每一种组合都会变成一个格子。

九座城市 × 三个品类,就是九行三列。

这是 groupby 给不了的东西。groupby 只会给你一张"城市+品类"的长表,还得自己 reshape 才能变宽。

算出来的占比是:手机 81.9%、耳机 15.6%、配件 2.4%,九座城市几乎一模一样,最多差两三个百分点。

这一问什么都没发现,但排除了"品类结构异常"这个可能------真实分析里很大一部分工作就是这样,做了才知道没问题。

fill_value=0 别省。某个组合没卖出过,默认是 NaN,后面做除法或者求和会被 NaN 传染,整行都算不出来。

如果还想看更细,列同样能叠层,传个列表就行:

python 复制代码
pt = df.pivot_table(index="城市", columns=["渠道", "品类"],
                    values="销售额", aggfunc="sum", fill_value=0)

表会变成两组列。这时候一个前面没看出来的问题冒头了:

plain 复制代码
========================================================================
列叠层:再加上渠道
========================================================================
按城市 × 渠道 × 品类(万元)
渠道   线上              线下
品类   手机  耳机 配件   手机  耳机  配件
城市
上海  588.2  34.7  5.3   20.9  81.4  12.8
南京  114.6  20.3  2.8  255.8  41.9   7.5
杭州  128.8  23.5  4.0  279.9  53.0   9.3

上海整体体量是南京的 1.68 倍,线下手机却只有南京的十二分之一,线上反而是五倍。

同一批需求,全被挤到线上去了。

单看一个维度,上海样样第一;交叉两个维度,才看见它有一个洞。 前面没发现,不是数据没问题,是维度不够。

6. 多个度量:金额、利润一起看

到这一步为止,看的都只是销售额。但卖得多不等于赚得多,判断好坏还得看利润。

派生指标得先在源表造好(毛利额在第 2 章已经加了),透视完再相除:

python 复制代码
pt = df.pivot_table(index="品类", values=["销售额", "毛利额"], aggfunc="sum")
pt["毛利率"] = pt["毛利额"] / pt["销售额"]
plain 复制代码
========================================================================
多个度量:金额和利润一起看
========================================================================
      毛利额  销售额  毛利率  金额占比
品类
手机   308.3  3853.3     8.0     0.826
耳机   243.3   695.0    35.0     0.149
配件    53.3   118.5    45.0     0.025
合计   604.8  4666.9    13.0     1.000

手机贡献了八成销售额,毛利率只有 8%;配件卖了不到 3%,毛利率 45%。整个盘子的毛利率被手机拖到 13%。

这里有个坑值得单独说:毛利率不能用 aggfunc="mean" 算。平均值算的是"每一笔订单的毛利率",小单和大单权重一样,结果会明显偏离真实值。

正确做法是把毛利额和销售额分别求和再相除,也就是上面这两行代码。

透视表负责加总,比值这种运算放在它外面做。

至于占比,透视表本身不生成百分比,就是拿结果除以总计:pt["销售额"] / pt["销售额"].sum()。

7. 只看关心的部分:筛选、排序、取前 N

还剩三种日常操作,代码都只有一行。

筛选是先把数据过滤掉再透视,对应 Excel 里的"筛选区域"------它不增加表格维度,只缩小数据范围:

python 复制代码
flt = df.query("渠道 == '线下' and 城市 in ['上海', '南京', '杭州']")

排序和取前 N 是透视完直接排。注意这是基于计算结果筛行,源数据里做不到:

python 复制代码
top = (df.pivot_table(index="门店", values="销售额", aggfunc="sum")
         .sort_values("销售额", ascending=False).head(10))
plain 复制代码
========================================================================
筛选与排序:取前 10 门店
========================================================================
          销售额
门店
上海-2店   379.7
上海-1店   363.6
北京-2店   363.5
北京-1店   362.8
广州-1店   299.9
广州-2店   281.1
深圳-2店   279.8
深圳-1店   277.6
杭州-1店   257.0
杭州-2店   241.5

跑出来第一名是上海-2店 379.7 万,上海-1店 363.6 万排第二,后面北京两家店咬得很紧,跟第二名只差一千块。

刷新在 pandas 里没有对应概念,但道理相通:透视结果只是源数据的视图,数据变了重跑一次就行,参数不用改。

把逻辑封成函数,追加 4 月数据再调一次,总额从 4666.9 万变成 6053 万,占比几乎没动。

python 复制代码
def region_report(data):
    p = data.pivot_table(index="大区", values="销售额", aggfunc="sum")
    p["占比"] = p["销售额"] / p["销售额"].sum()
    return p

8. 它擅长什么,不擅长什么

回头看,所有能力都装在那四个格子里:

  1. 行管纵向分组,可以叠多层往下钻;
  2. 列管横向展开,也能叠层形成交叉;
  3. 值放度量,可以放多个各用各的算法;
  4. 筛选切范围,不动表结构但全表联动。

它真正的长处是换视角便宜。上面七次追问是连续追下来的,每一次只改一两个参数,没有一次需要重写逻辑。

手写 SQL 得写七条语句,做静态汇总表得重排七遍格式。

当然,它的短板也清楚,主要四条。

  1. 它丢掉行顺序。 输出按分组重新组织,不保留原始顺序。凡是要看相邻两行关系的计算------环比、连续涨跌、滚动窗口、形态识别------都得在明细层面算完再透视。
  2. 它不留明细。 一旦聚合成格子,原始订单号就没了,想追某个数字背后的订单只能回源表。
  3. 它不做跨列运算。 毛利率、占比、同比这类指标,要么在源表造好派生列,要么在透视结果外面再算一步。
  4. 它格式能力很弱。 带页眉页脚、固定版式的对外报表做不了,它是分析工具,不是排版工具。

所以合理的分工是:用透视表探索,找到结论之后,再把关键的那一两个视图固化成报表或者脚本。

文中使用的代码共享在:pivot_demo.py: url11.ctfile.com/f/45455611-...

相关推荐
5233AI开发笔记12 分钟前
API 返回 429:先区分限速、并发和配额,再决定是否重试
python
特立独行的猫A37 分钟前
我用仓颉写了个多平台发文工具:一份 Markdown,一条命令发六个平台
java·后端·python
陆陆陆六1 小时前
# 五个AI引擎的答案采集:反爬、去重与时间窗对齐的三个工程细节
python·net·ai答案采集
GrowingIO技术社区2 小时前
CDP、行为分析、归因与营销自动化有什么区别?
数据结构·数据挖掘·数据分析
GrowingIO技术社区2 小时前
实时标签怎样验收?从数据更新到分群可用
数据结构·数据挖掘·数据分析
小小张说故事3 小时前
Python 的 a[1:5:2] 到底切出了什么?拆开 slice 对象、__getitem__ 与负步长那次反向
python
wuyk5554 小时前
PyQt6-第15讲-数据可视化QChart
python·ui·信息可视化·pyqt
天天被压力4 小时前
【A股分钟级高频数据完全手册 #04】指数分钟级行情数据接口:沪深指数Pro版最新分时取数
java·人工智能·python
日日大王学编程4 小时前
2026主流AI Agent产品分类、核心差异与技术特性全解析
人工智能·后端·python