用几个数说清一列

完整讲解 · 8 段教学·配 3 道练习题·预计 45 分钟

本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。

教学 01 / 08

前言

上一章学会了从表里挑出想要的数据。可是挑出来以后,还得把它说清楚才行。比如记账本里一千多笔花销,一笔一笔地看,看到眼花,也说不出个所以然。要是有人问你「你平时一笔花多少钱」,总不能把整本账递过去吧?

这时候,就得用几个数,把一整列说清楚:一般是多少?最多的是哪种?大的有多大?波动又有多大?这几个数,在统计学里叫描述统计。

其实第 2 章用 describe() ,已经一下子算出了好几个,还发现了一件怪事:平均每笔 51 元,可一半的花销都不超过 21 元。当时说这一点第 4 章还会细讲,指的就是这一章。所以这一章,就把这几个数一个一个拆开,弄清楚它们各自在说什么,什么时候该信哪一个。

学这一章之前,需要先学完 数据分析课第 3 章 。

目录

这一章会学到:

  • 一、平均数和中位数——「一般是多少」该信哪个
  • 二、数一数——value_counts() ,以及占多大比例
  • 三、分位数——大的有多大,quantile() 和 nlargest()
  • 四、标准差——波动有多大
  • 五、常见坑
  • 六、自己查一次文档——用 value_counts() 把金额分成几段
  • 七、小结与回看

学完这一章,别人问起「你平时一笔花多少钱」,你能用几个数答得又准又清楚。

教学 02 / 08

一、平均数和中位数

先来解开第 2 章留下的那个疑问:平均数和中位数,为什么会差那么多?别急着看记账本,先用四个数做个小实验。这里要用到 pd.Series() ,它能直接把一个列表变成一列:

Python· 可在网页运行
import pandas as pd

s = pd.Series([10, 20, 30, 1000])
print(s.mean())
print(s.median())

输出的结果:

文本
265.0
25.0

两个数差了一大截。它们各是怎么算出来的呢?

  • mean() 算的是平均数:把所有数加起来,再除以个数。(10 + 20 + 30 + 1000) ÷ 4 = 265。
  • median() 算的是中位数:先从小到大排好,再取正中间的那个数。个数是双数的时候,就取中间两个的平均,(20 + 30) ÷ 2 = 25。

各位童鞋发现没有?四个数里有三个都不超过 30,平均数却是 265,没有一个数接近它。为什么会这样?就是因为那一个 1000,把平均数拉了上去。中位数就不一样了,它只看排在中间的是谁。最大的那个数是 1000 也好,100 万也好,中位数都不受影响。

小实验看明白了,再回到记账本。这回分两次算:先算全部的花销,再把房租去掉,算一遍。

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(round(ledger['cost'].mean(), 2))
print(ledger['cost'].median())

no_rent = ledger[ledger['item'] != '房租']
print(round(no_rent['cost'].mean(), 2))
print(no_rent['cost'].median())

输出的结果:

文本
51.1
21.0
29.21
21.0

对比一下:去掉一年的 12 笔房租以后,平均数从 51 元一下子掉到了 29 元;中位数却纹丝不动,还是 21 元。

所以,「平时一笔花多少」这个问题,中位数答得更准:21 元左右。那平均数是不是就没用了呢?也不是。平均数乘上笔数,就是总额,所以算总账离不开它。而且,两个数差得越远,说明数据里「特别大」或「特别小」的值越多,这本身就是一条值得注意的信息。

把两者放在一起,比一比:

平均数 mean()中位数 median()
怎么算加起来除以个数排好序取中间
受特别大、特别小的值影响吗受,影响很大几乎不受
适合回答一共多少、平均分到每一个是多少一般情况是多少

先做一道练习:晚饭一般花多少?用平均数和中位数各算一遍。

练习 1 / 3晚饭一般花多少

读进记账本,挑出名称(item)是「晚饭」的那些金额,分两行打印:平均数(用 round() 保留两位小数)和中位数。

运行后的输出应该是:

文本
39.58
40.0
💡 提示(3 条)
  1. 先挑行再取列:ledger[ledger['item'] == '晚饭']['cost']

  2. 平均数 round(dinner.mean(), 2),中位数 dinner.median()

  3. 完整代码:

    Python
    import pandas as pd
    
    ledger = pd.read_csv('ledger_2026.csv')
    dinner = ledger[ledger['item'] == '晚饭']['cost']
    print(round(dinner.mean(), 2))
    print(dinner.median())
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
教学 03 / 08

二、数一数

平均数和中位数,都是拿数字来算的。可是类别、付款方式这些列是文字,没法求平均,那该怎么说清楚呢?办法就是数一数:每一种各出现了几次。

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['category'].value_counts())

输出的结果:

文本
category
餐饮    984
交通    241
娱乐     46
居住     36
购物     29
学习     11
医疗      7
Name: count, dtype: int64

value_counts() 会数出每个值出现了几次,还会自动按从多到少排好。还记得第 2 章 describe() 里的 top 和 freq 吗?它们其实就是这张表的第一行:餐饮,984 次。

不过,光看次数,不太容易感觉出到底占了多少。这时候加上 normalize=True ,次数就变成了比例:

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['category'].value_counts(normalize=True).round(3))

输出的结果:

文本
category
餐饮    0.727
交通    0.178
娱乐    0.034
居住    0.027
购物    0.021
学习    0.008
医疗    0.005
Name: proportion, dtype: float64

这些比例加起来是 1。代码和输出里,还有两个小地方要说一下:

  • round(3) 是 Series 的方法,会把每个值都保留三位小数。
  • 最下面的名字,也从 count 变成了 proportion ,就是「比例」的意思。

回到餐饮那一行,0.727 的意思是:一年里每 100 笔花销,大约有 73 笔是吃饭。可这样的小数,读起来总不如百分数直观。想写成百分数,可以用 f-string 的 % 格式:

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
shares = ledger['category'].value_counts(normalize=True)
print(f"餐饮占 {shares['餐饮']:.1%}")

输出的结果:

文本
餐饮占 72.7%

shares['餐饮'] 是按名字取出一个值。后面的 :.1% 会把它乘以 100,保留一位小数,再加上百分号。

先做一道练习,用同样的办法,看看各种付款方式各占几成。

练习 2 / 3各种付款方式各占几成

读进记账本,数一数每种付款方式(payment)占全部笔数的比例,保留三位小数,打印出来。

运行后的输出应该是:

文本
payment
支付宝    0.430
微信      0.425
银行卡    0.137
现金      0.009
Name: proportion, dtype: float64
💡 提示(3 条)
  1. value_counts() 加上 normalize=True 就是比例

  2. 最后接上 .round(3)

  3. 完整代码:

    Python
    import pandas as pd
    
    ledger = pd.read_csv('ledger_2026.csv')
    print(ledger['payment'].value_counts(normalize=True).round(3))
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
教学 04 / 08

三、分位数

中位数,说的是「排在正中间的那个」。把这个想法推广一下:排在四分之一处的、排在十分之九处的,又各是多少呢?这些数叫分位数,用 quantile() 来算:

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['cost'].quantile(0.5))
print(ledger['cost'].quantile([0.25, 0.75, 0.9]))

输出的结果:

文本
21.0
0.25     9.625
0.75    32.000
0.90    55.500
Name: cost, dtype: float64

先看第一行:quantile(0.5) 算出来是 21,它就是中位数。括号里要是放一个列表,就能一次算出好几个。下面这几个数,读法是这样的:

  • 0.25 对应 9.625:四分之一的花销不超过 9.6 元左右,像早饭、地铁这类小钱。
  • 0.75 对应 32:四分之三的花销不超过 32 元。
  • 0.9 对应 55.5:十笔里有九笔不超过 55.5 元。

有的童鞋可能会问:账本里真有 9.625 元这笔花销吗?其实并没有。排在四分之一处的那个位置,正好落在两笔花销之间,pandas 就按比例,在两者之间取了一个值。

分位数告诉你的,是「大的有多大」。那最大的几笔,具体都是什么呢?这时候用 nlargest() :第一个参数是要几行,第二个参数是按哪一列来比。

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
no_rent = ledger[ledger['item'] != '房租']
print(no_rent.nlargest(5, 'cost'))

输出的结果:

文本
            date  item category   cost payment
1140  2026-11-06  衣服     购物  391.0  支付宝
1317  2026-12-23  衣服     购物  388.5    微信
695   2026-07-09  衣服     购物  383.5  支付宝
1256  2026-12-08  衣服     购物  364.5  支付宝
566   2026-06-02  衣服     购物  317.0    微信

注意,这里先去掉了房租。为什么呢?因为不去掉的话,前 12 名全是房租。去掉以后就能看出:除了房租,最大的几笔都是买衣服,而且都在三四百元。

反过来,要找最小的几笔,就换成 nsmallest() 。

教学 05 / 08

四、标准差

前面三节,回答的是「一般是多少」「最多的是哪种」「大的有多大」。前言里列的几个问题,还剩最后一个:波动有多大?先来想一想:平均数一样,情况就一样吗?

想象有两个人:一个每顿饭都花 30 元;另一个有时花 10 元,有时花 50 元,平均下来也是 30 元。这两个人的平均数一样,可第二个人花钱忽多忽少,就更难做预算。

衡量这种「忽多忽少」的数,叫标准差,用 std() 来算。刚开始,可以大致这样理解它:每个值离平均数,一般差多少。差得越多,标准差就越大。

拿一日三餐来比一比:

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
for meal in ['早饭', '午饭', '晚饭']:
    cost = ledger[ledger['item'] == meal]['cost']
    print(meal, round(cost.mean(), 2), round(cost.std(), 2), cost.min(), cost.max())

输出的结果:

文本
早饭 10.65 2.63 6.0 15.0
午饭 26.4 4.98 18.0 35.0
晚饭 39.58 11.69 20.0 59.5

每一行依次是:平均数、标准差、最少、最多。挑早饭和晚饭来对比一下:

  • 早饭平均 10.65 元,标准差 2.63,一般就在 8 到 13 元上下,最少 6 元,最多 15 元,很稳定。
  • 晚饭平均 39.58 元,标准差 11.69,有时二十出头,有时将近 60 元,忽多忽少。

光看平均数,只知道晚饭比早饭贵;加上标准差,才知道晚饭的钱最不好预算。所以要省钱,从晚饭下手最有空间。

最后再说一句这段代码。上面用 for 循环,把同样的事情对三种饭各做了一遍。其实「按某一列分成几组,每组各算一遍」这种事,pandas 有更省事的办法,第 6 章会讲。

教学 06 / 08

五、常见坑

这几个统计量,算起来都只要一行代码。可正因为算得太容易,一不留神,算出来的数就会误导你。下面这三个坑,都不会报错,所以更要留心。

1、空着的值被悄悄跳过了

books.csv 里,有的书还没有人打分,rating 这一列就空着。那平均评分会怎么算呢?试一下:

Python· 可在网页运行
import pandas as pd

books = pd.read_csv('books.csv')
print(len(books))
print(books['rating'].count())
print(round(books['rating'].mean(), 2))

输出的结果:

文本
120
109
3.55

120 本书,有评分的只有 109 本。可 mean() 并没有报错,因为它跳过了空值,算的是这 109 本书的平均。

这通常正是我们想要的,因为没人打分的书,不该当成 0 分来算。可你得知道:这个平均数背后,是 109 本书,而不是 120 本。

不光是 mean() ,value_counts() 也一样,默认不数空值。想把空值也数进去,就加上 dropna=False :

Python· 可在网页运行
import pandas as pd

books = pd.read_csv('books.csv')
print(books['rating'].value_counts(dropna=False).head(3))

输出的结果:

文本
rating
4.0    13
NaN    11
2.8     8
Name: count, dtype: int64

这里的 NaN ,就是 pandas 里的空值,意思是「不是一个数」。可以看到,空着的有 11 本,排在第二,只比 4.0 分的 13 本少,比其他任何一个分数的书都多。这一点,要是不把空值数进来,是看不出来的。空值该怎么处理,第 5 章清洗数据时会专门讲。

2、按笔数和按金额,结论不一样

再来看一个问题:「我最常用什么付钱?」分别按笔数和按金额,各算一遍:

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['payment'].value_counts())
print(ledger.groupby('payment')['cost'].sum().sort_values(ascending=False))

输出的结果:

文本
payment
支付宝    582
微信      575
银行卡    185
现金       12
Name: count, dtype: int64
payment
银行卡    34442.5
支付宝    19207.0
微信      15259.5
现金        286.5
Name: cost, dtype: float64

按笔数,支付宝第一;按金额,却是银行卡第一。为什么呢?因为房租这种大钱,都是用银行卡交的。

两个结论都对,只是回答的是不同的问题。所以说「最常用」之前,先想清楚:说的是「用的次数最多」,还是「花的钱最多」?

3、四舍五入以后,比例加起来不是 1

第二节说过,比例加起来是 1。可要是先四舍五入了呢?

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
shares = ledger['payment'].value_counts(normalize=True).round(3)
print(shares)
print(shares.sum())

输出的结果:

文本
payment
支付宝    0.430
微信      0.425
银行卡    0.137
现金      0.009
Name: proportion, dtype: float64
1.001

每个比例都往上或者往下凑了一点,加起来就多出了 0.001。所以,要是还要拿比例接着算,就用没有四舍五入的原始值;round() 只在最后给人看的时候才用。

练习 3 / 3高分书占多少

在有评分的书里,评分达到 4.5 分及以上的占多少?读进 books.csv ,算出这个比例,用 round() 保留三位小数打印。

留意分母:没有评分的书不该算进去。

运行后的输出应该是:

文本
0.183
💡 提示(3 条)
  1. (books['rating'] >= 4.5).sum() 数出 4.5 分及以上的有几本

  2. 有评分的本数是 books['rating'].count(),不是 len(books)

  3. 完整代码:

    Python
    import pandas as pd
    
    books = pd.read_csv('books.csv')
    high = (books['rating'] >= 4.5).sum()
    print(round(high / books['rating'].count(), 3))
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
教学 07 / 08

六、自己查一次文档

value_counts() 能数文字,那能不能拿来数金额呢?问题是,金额几乎每一笔都不一样,直接数的话,会数出几百种来。更有用的做法,是把金额分成几段:10 元以下的有几笔,10 到 20 元的有几笔……这能不能做到呢?去看看文档:Series.value_counts 。

它的参数一共有五个:

  • normalize 、dropna ,前面已经用过了。
  • sort 默认是 True ,按次数排序;设成 False ,就按原来的顺序。
  • ascending 管的是从小到大排,还是从大到小排。
  • 还有一个 bins 。文档说,用了它,就不再数每个值,而是把它们分成几个半开区间来数,这是 pd.cut 的简便写法,只能用在数字上。它的类型写的是 int ,也就是分成几段。

看起来,bins 就是咱们要找的。那就试一试,分成 4 段:

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['cost'].value_counts(bins=4))

输出的结果:

文本
(0.502, 627.25]      1342
(1875.75, 2500.0]      12
(627.25, 1251.5]        0
(1251.5, 1875.75]       0
Name: count, dtype: int64

分是分了,可没什么用。为什么呢?因为它是把最小值到最大值平均切成 4 段,每段六百多元宽,房租以外的花销,全挤在了第一段里。

咱们想要的,是自己定边界,比如 10、20、50、100。这又该怎么写呢?文档里提到,它是 pd.cut 的简便写法。那就顺着点进 pandas.cut ,看看它的 bins 参数。原来,除了写一个整数,还可以写一串数。这串数就是每一段的边界,各段可以不一样宽。

那就试试把边界传进去:

Python· 可在网页运行
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['cost'].value_counts(bins=[0, 10, 20, 50, 100, 3000], sort=False))

输出的结果:

文本
(-0.001, 10.0]     363
(10.0, 20.0]       262
(20.0, 50.0]       557
(50.0, 100.0]      108
(100.0, 3000.0]     64
Name: count, dtype: int64

这下清楚了:20 到 50 元的最多,有 557 笔;超过 100 元的只有 64 笔。这里还加了一个 sort=False ,让各段按金额从小到大排,读起来更顺。

再说一下输出里的写法。(10.0, 20.0] 是半开区间:圆括号表示不包括 10,方括号表示包括 20。第一段写成了 (-0.001, 10.0] ,是 pandas 为了把正好等于 0 的值也算进去,往左挪了一点点,不用在意。

从这次查文档也能看出来:文档里一个参数的说明,有时只写了最常用的用法。顺着它提到的另一个函数看过去,往往能找到更多的门道。

教学 08 / 08

七、小结与回看

这一章,咱们用几个数,就把一整列说清楚了。回头梳理一下,学到了这几件事:

  • 平均数 mean() 是加起来除以个数,容易被特别大、特别小的值带偏;中位数 median() 取的是排在中间的值,几乎不受影响。说「一般情况」,中位数更靠谱;算总账,离不开平均数。
  • value_counts() 能数出每个值出现了几次。加上 normalize=True 就变成比例,round() 能保留几位小数,:.1% 能写成百分数。
  • quantile() 算分位数,比如 0.9 表示十个里有九个不超过它;nlargest() 、nsmallest() 能找出最大、最小的几行。
  • 标准差 std() 衡量的是忽多忽少:每个值离平均数,一般差多少。
  • 常见的坑:空值会被悄悄跳过,要留意背后的个数;按笔数和按金额,结论不一样;四舍五入只在最后做。
  • 查文档学会了用 value_counts(bins=...) 把金额分段,还顺着 pd.cut 的文档,学会了自己定边界。

哪里卡住了,就回去看一看:

  • f-string:第 2 章
  • for 循环:第 5 章

到这里,咱们用的都是一份干干净净的账本。可现实里的数据,往往没有这么整齐。所以下一章换一份数据:一份还没整理过的账单。它的日期格式不统一,金额后面带着「元」,还有空着的、重复的、明显记错的。把它收拾干净,是数据分析里最花时间、也最见功夫的一步。

读完了?动手练一遍才算真会。

去闯关模式练习 →

学习目录