跳转至

如何分析数据

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

  1. 单变量分析
  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 换成人数。人数是外延量,可以加总、算份额,内涵量的麻烦就绕过去了,还能看到平均分看不到的两极分化。

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

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

这里可能有一个疑问,我们的原始数据是每一个人一行,然后每个人都有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

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

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

问题再换成「每个班各科均分」「男女各分数段人次」,宽表的循环要重写,长表只换 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 本身就是信息
)

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

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