---
description: "描述性分析就两种：单变量分析和分组聚合；先分清指标是外延的还是内涵的，再看辛普森悖论、透视表与长宽表；附一份成绩长表，用 pandas pivot_table 走完单维度分布和联合分析两篇案例"
copy_source: true
---

# 如何分析数据

最近做数分容易找不到切入口，所以整理了一下应该怎么分析。统计学中描述性分析就是2种：

1. 单变量分析
2. 分组聚合

有的地方认为关联分析也是描述性统计，但我更倾向把关联分析作为推断统计。

## 单变量分析

是描述性统计的起点，也是统计学的起点。看起来比较简单，count、mean、std 这些，就像在数数一样。

这里面藏了一个小知识点，就是指标是内涵的还是外延的，比如A学校10个班，每个班有一个平均分，平均分这个指标就是内涵的，因为无法加总，也无法直接加总求平均。

常见的指标都是外延的，比如每个班的人数，或者公司每个部门的收入，是可以加在一起的。

但是这个知识点之上还有一个知识点，就是即便是外延的指标，加总起来也不见得有意义，比如你把一个班的所有人考试分数加在一起，得出这个班的总得分，并不能代表班级质量。但有些时候加总有价值，比如所有人的体重总和，可以作为电梯是否超重的评价指标，体重的平均可以作为健康的评价指标。

!!! note "内涵量"
    本质上内涵量就是外延量的衍生产物，是两个外延量相除，所以对于内涵量取平均需要加权统计。常见的有：平均分、单价、温度、成功率：平均分 = 总分 ÷ 人数，成功率 = 成功例数 ÷ 手术例数。所以班级总分虽然单看没意义，却是平均分的分子；外延量一旦求平均，也就造出了新的内涵量，比如部门收入 ÷ 人数 = 人均收入。

## 分组聚合

就是分成n组，每一组内进行一次单变量分析。

有了分组就可以知道每个班的情况，从而知道哪个班平均分更高。

这里面也藏了两个知识点，第一个知识点是二次分组，举个例子，一次分组后，发现A班的平均分高于B班的平均分，看起来是A班更好、但是如果二次按照性别分组，于是有了2x2个组，发现A班的男生平均分低于B班的男生平均分，A班的女生平均分也更低，结论反而变成了B班更好。

这就是经典的**辛普森悖论**，AB两个医院，B医院大小手术成功率都更高，所以所有大手术（疑难）都来A医院做，最后整体A医院的成功率反而更低。

这个二是虚指，理论上可以3、4、5、6无限增加。于是实际会分成 m x n x p x q .... 个小组。本质上就是先生成组合标签，再根据这一列组合标签来分组罢了。另外还有个小特殊，标签不一定来自现成的列，也可以从 value 造出来。比如把分数切成优（≥90）、良（80–89）、及格（60–79）、不及格（<60）四段（叫**分箱**），分数段就成了一个新的定序分组特征，value 换成**人数**。人数是外延量，可以加总、算份额，内涵量的麻烦就绕过去了，还能看到平均分看不到的两极分化。

在二次分组的基础上又有两个知识点：

1. 一是组合标签之间可能是两种关系，可能是包含关系（比如省和市），也可能没关系（比如班级 x 性别），这会影响后面的数据可视化；
2. 二是如果**把一些分组特征的值作为列名**，就会产出数据透视表。比如年级成绩单，每一行是一个班（按照班级聚合），然后每一列（语文、数学、英语）其实就是第二个聚合字段的值。

这里可能有一个疑问，我们的原始数据是每一个人一行，然后每个人都有3列成绩（语文、数学、英语），所以看起来没有`把一些分组特征的值作为列名`这个步骤，所以下面介绍一下数据的格式。

## 数据格式

用一个常见的小场景为例，数据可能有这样几列，姓名、班级、语文、数学、英语。
很容易注意到，班级 和 后面三个科目并不是并行关系，因为班级是一个分组的依据，班级和科目才是平级的。所以我们可以转换成`长表`。
列名就是 姓名、班级、科目、分数。这样一来，班级和科目都是分组的依据了，每一行就只有一个值，前面的都是值的标签。

我们可以分析一班的平均分，语文的平均分、也可以分析一班的语文平均分。

总之长表用来做分析，宽表用来给人看。

有了这个数据格式之后，可以进行更多的分析：

后面两篇案例都用同一份数据 [`scores.csv`](assets/scores.csv)：3 个年级、8 个班、277 名学生，每人语文、数学、英语三科，一共 831 行长表。

| 姓名 | 性别 | 年级 | 班级 | 科目 | 分数 | 分数段 |
|:--|:--|:--|:--|:--|--:|:--|
| 龚佳 | 女 | 高一 | 高一1班 | 语文 | 100 | 优 |
| 龚佳 | 女 | 高一 | 高一1班 | 数学 | 98 | 优 |
| 龚佳 | 女 | 高一 | 高一1班 | 英语 | 93 | 优 |
| 万健磊 | 男 | 高一 | 高一1班 | 语文 | 61 | 及格 |

一行 = 一个人的一科，前面六列都是标签，只有「分数」是值。注意数出来的是**人次**：一个人三科占三行，要数人数得按姓名去重。

### 同一个问题，宽表要写循环，长表只改参数

问：每一科各分数段有多少人次？

宽表（每人一行，三科三列）里，科目藏在列名上，没法拿来分组，只能一科一科地循环：

```python
# wide：班级、姓名、语文、数学、英语
def band(s):
    return pd.cut(s, [0, 60, 80, 90, 101], right=False, labels=["不及格", "及格", "良", "优"])

parts = []
for subj in ["语文", "数学", "英语"]:
    parts.append(band(wide[subj]).value_counts().rename(subj))
pd.concat(parts, axis=1).T
```

长表里科目就是一列普通的标签：

```python
df.pivot_table(index="科目", columns="分数段", aggfunc="size")
```

问题再换成「每个班各科均分」「男女各分数段人次」，宽表的循环要重写，长表只换 `index` / `columns` 两个参数。宽表转长表用 `wide.melt(id_vars=["班级", "姓名"], var_name="科目", value_name="分数")`；反过来，宽表本身就是长表的一次 pivot：`df.pivot_table(index=["班级", "姓名"], columns="科目", values="分数")` —— 这就是前面说的**把分组特征的值变成列名**。要把两科交叉起来看（语文优的人英语怎么样）时，就得这样转回宽表，见[单维度分布](distribution.md#two-features)。

### 一个模板贯穿全部案例

```python
df.pivot_table(
    index=...,       # 行：按哪些标签分组（可以多个）
    columns=...,     # 列：哪些标签的值展开成列名（可以不写）
    values=...,      # 值：对哪一列做聚合（数个数时不写）
    aggfunc=...,     # 怎么聚合：size / nunique / sum / mean / median / std / min / max
    observed=False,  # 分类标签的空组合也保留，0 本身就是信息
)
```

换一个分析角度，就是换一组参数。两篇案例的每张图都先给这一段代码和它跑出来的结果表，再画图：

- [单维度分布：什么情况画什么图](distribution.md) —— 一个特征用环形图，两个平级特征（同一个学生的语文段 × 英语段）用热力图加堆叠条形图，三个以上先拆开看、再看笛卡尔积（尾部合并成「其他」），有包含关系用矩形树图和旭日图
- [联合分析：数值和占比、外延和内涵](cross-analysis.md) —— 不及格人次最多的班，班内占比反而不是最高；总分为什么不能比；年级均分为什么要加权；均分一样，分布可以完全不同
