如何分析数据
最近做数分容易找不到切入口,所以整理了一下应该怎么分析。统计学中描述性分析就是2种:
- 单变量分析
- 分组聚合
有的地方认为关联分析也是描述性统计,但我更倾向把关联分析作为推断统计。
单变量分析
是描述性统计的起点,也是统计学的起点。看起来比较简单,count、mean、std 这些,就像在数数一样。
这里面藏了一个小知识点,就是指标是内涵的还是外延的,比如A学校10个班,每个班有一个平均分,平均分这个指标就是内涵的,因为无法加总,也无法直接加总求平均。
常见的指标都是外延的,比如每个班的人数,或者公司每个部门的收入,是可以加在一起的。
但是这个知识点之上还有一个知识点,就是即便是外延的指标,加总起来也不见得有意义,比如你把一个班的所有人考试分数加在一起,得出这个班的总得分,并不能代表班级质量。但有些时候加总有价值,比如所有人的体重总和,可以作为电梯是否超重的评价指标,体重的平均可以作为健康的评价指标。
内涵量
本质上内涵量就是外延量的衍生产物,是两个外延量相除,所以对于内涵量取平均需要加权统计。常见的有:平均分、单价、温度、成功率:平均分 = 总分 ÷ 人数,成功率 = 成功例数 ÷ 手术例数。所以班级总分虽然单看没意义,却是平均分的分子;外延量一旦求平均,也就造出了新的内涵量,比如部门收入 ÷ 人数 = 人均收入。
分组聚合
就是分成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 换成人数。人数是外延量,可以加总、算份额,内涵量的麻烦就绕过去了,还能看到平均分看不到的两极分化。
在二次分组的基础上又有两个知识点:
- 一是组合标签之间可能是两种关系,可能是包含关系(比如省和市),也可能没关系(比如班级 x 性别),这会影响后面的数据可视化;
- 二是如果把一些分组特征的值作为列名,就会产出数据透视表。比如年级成绩单,每一行是一个班(按照班级聚合),然后每一列(语文、数学、英语)其实就是第二个聚合字段的值。
这里可能有一个疑问,我们的原始数据是每一个人一行,然后每个人都有3列成绩(语文、数学、英语),所以看起来没有把一些分组特征的值作为列名这个步骤,所以下面介绍一下数据的格式。
数据格式
用一个常见的小场景为例,数据可能有这样几列,姓名、班级、语文、数学、英语。 很容易注意到,班级 和 后面三个科目并不是并行关系,因为班级是一个分组的依据,班级和科目才是平级的。所以我们可以转换成长表。 列名就是 姓名、班级、科目、分数。这样一来,班级和科目都是分组的依据了,每一行就只有一个值,前面的都是值的标签。
我们可以分析一班的平均分,语文的平均分、也可以分析一班的语文平均分。
总之长表用来做分析,宽表用来给人看。
有了这个数据格式之后,可以进行更多的分析:
后面两篇案例都用同一份数据 scores.csv:3 个年级、8 个班、277 名学生,每人语文、数学、英语三科,一共 831 行长表。
| 姓名 | 性别 | 年级 | 班级 | 科目 | 分数 | 分数段 |
|---|---|---|---|---|---|---|
| 龚佳 | 女 | 高一 | 高一1班 | 语文 | 100 | 优 |
| 龚佳 | 女 | 高一 | 高一1班 | 数学 | 98 | 优 |
| 龚佳 | 女 | 高一 | 高一1班 | 英语 | 93 | 优 |
| 万健磊 | 男 | 高一 | 高一1班 | 语文 | 61 | 及格 |
一行 = 一个人的一科,前面六列都是标签,只有「分数」是值。注意数出来的是人次:一个人三科占三行,要数人数得按姓名去重。
同一个问题,宽表要写循环,长表只改参数
问:每一科各分数段有多少人次?
宽表(每人一行,三科三列)里,科目藏在列名上,没法拿来分组,只能一科一科地循环:
# 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
长表里科目就是一列普通的标签:
问题再换成「每个班各科均分」「男女各分数段人次」,宽表的循环要重写,长表只换 index / columns 两个参数。宽表转长表用 wide.melt(id_vars=["班级", "姓名"], var_name="科目", value_name="分数");反过来,宽表本身就是长表的一次 pivot:df.pivot_table(index=["班级", "姓名"], columns="科目", values="分数") —— 这就是前面说的把分组特征的值变成列名。要把两科交叉起来看(语文优的人英语怎么样)时,就得这样转回宽表,见单维度分布。
一个模板贯穿全部案例
df.pivot_table(
index=..., # 行:按哪些标签分组(可以多个)
columns=..., # 列:哪些标签的值展开成列名(可以不写)
values=..., # 值:对哪一列做聚合(数个数时不写)
aggfunc=..., # 怎么聚合:size / nunique / sum / mean / median / std / min / max
observed=False, # 分类标签的空组合也保留,0 本身就是信息
)
换一个分析角度,就是换一组参数。两篇案例的每张图都先给这一段代码和它跑出来的结果表,再画图:
- 单维度分布:什么情况画什么图 —— 一个特征用环形图,两个平级特征(同一个学生的语文段 × 英语段)用热力图加堆叠条形图,三个以上先拆开看、再看笛卡尔积(尾部合并成「其他」),有包含关系用矩形树图和旭日图
- 联合分析:数值和占比、外延和内涵 —— 不及格人次最多的班,班内占比反而不是最高;总分为什么不能比;年级均分为什么要加权;均分一样,分布可以完全不同