5-3 透视表与交叉表:报告的排版工序 本节摘要:pivottable把长表转成行列双维的横排报告,crosstab专做频次交叉,melt负责把宽表摊回长表。本节的核心是理解长宽两种数据形态的互换,这是报告排版与下游建模之间的摆渡船。 会诊指标算出来了(5-2),但那还是"每行一个门店"的长条表。管理层习惯看的是横排版:门店做行、渠道做列、格子里的金额。排版这道工序,就是长宽表的互换。 pivottable:两维排版的会诊报告 三个关键参数:margins补合计行、fillvalue补缺格、aggfunc换指标: ⚠️ 常见坑:pivottable的aggfunc默认是mean不是sum,而且不告警。
本节摘要:pivot_table把长表转成行列双维的横排报告,crosstab专做频次交叉,melt负责把宽表摊回长表。本节的核心是理解长宽两种数据形态的互换,这是报告排版与下游建模之间的摆渡船。
会诊指标算出来了(5-2),但那还是"每行一个门店"的长条表。管理层习惯看的是横排版:门店做行、渠道做列、格子里的金额。排版这道工序,就是长宽表的互换。
import pandas as pd df = pd.DataFrame({ '门店': ['A', 'A', 'B', 'B', 'C', 'A', 'B', 'C'], '渠道': ['线上', '线下', '线上', '线上', '线下', '线上', '线下', '线上'], '月份': ['1月'] * 4 + ['1月'] * 2 + ['2月'] * 2, '金额': [1280, 960, 1732, 880, 2100, 1150, 990, 860], }) # 行=门店 列=渠道 值=金额和 report = df.pivot_table(index='门店', columns='渠道', values='金额', aggfunc='sum') print(report) # 渠道 线上 线下 # 门店 # A 2430 960.0 # B 1732 990.0 # C NaN 2100.0 ← C线上没开张
三个关键参数:margins补合计行、fill_value补缺格、aggfunc换指标:
print(df.pivot_table(index='门店', columns='渠道', values='金额', aggfunc='sum', margins=True, margins_name='合计', fill_value=0)) # 渠道 线上 线下 合计 # 门店 # A 2430 960 3390 # B 1732 990 2722 # C 0 2100 2100 # 合计 4162 4050 8212
⚠️ 常见坑:pivot_table的aggfunc默认是mean不是sum,而且不告警。金额透视忘了写aggfunc='sum',报告里的数全成了均值——这是新手透视表事故的第一名。另外它的近亲pivot(无聚合版)要求行列组合唯一,有重复就抛错,遇到时说明该聚合了。
# 月份塞进列的上一层,一张表装下两个月 wide = df.pivot_table(index='门店', columns=['月份', '渠道'], values='金额', aggfunc='sum', fill_value=0) print(wide) # 月份 1月 2月 # 渠道 线上 线下 线上 线下 # 门店 # A 1280 960 1150 0 # B 2612 0 0 990 # C 0 2100 0 860
列变成多层索引——第02章2-3的分层知识在此兑现:wide['1月']直接取整个月块,wide.loc[:, ('1月', '线上')]定位单列。
# 不填values时,crosstab数的是频次:每类组合几条记录 print(pd.crosstab(df['门店'], df['渠道'])) # 渠道 线上 线下 # 门店 # A 2 1 # B 1 1 # C 1 1 # normalize='index':行内占比,一眼看出渠道结构 print(pd.crosstab(df['门店'], df['渠道'], normalize='index').round(2)) # 渠道 线上 线下 # 门店 # A 0.67 0.33 # B 0.50 0.50 # C 0.50 0.50
crosstab本质是"透视表数频次"的快捷方式,渠道结构、转化漏斗这类"看构成"的报告用它最快。
下游的绘图库与建模管线大多吃长表(每行一个观测),报告给人看是宽表,两者之间靠melt摆渡:
long = wide.reset_index().melt(id_vars='门店', var_name=['月份', '渠道'], value_name='金额') print(long.head(4)) # 门店 月份 渠道 金额 # 0 A 1月 线上 1280 # 1 B 1月 线上 2612 # 2 C 1月 线上 0 # 3 A 1月 线下 960

排版工序的最后一条经验:透视出的报告表在交付前补两个动作——给索引与列名起有业务含义的名字(rename_axis),以及把关键数字round到位数。机器不在乎这些,看报告的人在乎,而报告的最终读者永远是人。
一张大透视表看着信息量大,但格子越多、每格样本越少,数字越不可信。门店乘渠道乘月份,三维一上,几十行的数据摊到上百个格子里,大半格子是小样本或空格。排版前先做一道算术:总记录数除以格子数,平均每格不到五条的透视表不值得给管理层看。要么降维(先只看门店乘渠道),要么先filter掉小样本组(上一节的器械),要么在格子里放count让人看见样本量。报表的美德是诚实,不是密集。
# 在报告里附上样本量,让读者自己判断可信度 counts = df.pivot_table(index='门店', columns='渠道', values='金额', aggfunc='count', fill_value=0) print(counts) # 渠道 线上 线下 # 门店 # A 2 1 # B 1 1 # C 1 1
金额表配样本量表,两张一起出,这是我在实战里固定的报告姿势。
单表报告到此完工。下一章把订单、会员、门店多张表归并成一份完整病史。