认识 DataFrame

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

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

教学 01 / 08

前言

各位童鞋还记得吗?上一章,只用三行 pandas 代码,就回答了「钱都花到哪儿去了」。那三行里,最要紧的其实是第一步:pd.read_csv() 读出来的那张表,也就是 DataFrame。为什么说它最要紧呢?因为后面所有的筛选、统计、分组、画图,都是在这张表上做的。

所以这一章先不急着算,而是先把这张表认清楚:它由哪几部分组成?有多少行、多少列?每一列装的是什么类型的数据?里面有没有奇怪的值?这一步,就是六步里的「探索」。

有的童鞋可能会想:看个表有什么好学的,直接算不就行了?其实,拿到一份新数据,先花几分钟把它看清楚,比一上来就埋头计算要省事得多。很多分析出了错,追查到最后,都是因为一开始没发现:某一列存的是文字,而不是数字;或者某一列有一大半是空的。

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

目录

这一章会学到:

  • 一、读进一张表——read_csv() 、head() 和 tail()
  • 二、看清表的样子——行数、列名、数据类型和 info()
  • 三、一列就是一个 Series——取出一列,算一算
  • 四、describe() ——一眼看全局
  • 五、常见坑
  • 六、自己查一次文档——让 describe() 也看看文字列
  • 七、小结与回看

学完这一章,拿到任何一份 csv,你都能在几分钟内说清楚:它有多少行、有哪些列、每一列是什么样的。

教学 02 / 08

一、读进一张表

要认识一张表,最直接的办法就是把它打印出来看看。先把记账本读进来:

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

ledger = pd.read_csv('ledger_2026.csv')
print(ledger)

输出的结果:

文本
            date      item category    cost payment
0     2026-01-01      房租     居住  2500.0  银行卡
1     2026-01-01  水电燃气     居住   156.0  支付宝
2     2026-01-01      早饭     餐饮    13.0  支付宝
3     2026-01-01      午饭     餐饮    24.5    微信
4     2026-01-01      晚饭     餐饮    30.0    微信
...          ...       ...      ...     ...     ...
1349  2026-12-31      早饭     餐饮     6.5  支付宝
1350  2026-12-31      午饭     餐饮    27.5    微信
1351  2026-12-31      晚饭     餐饮    58.5    现金
1352  2026-12-31      地铁     交通     4.0    微信
1353  2026-12-31      零食     餐饮     9.5    微信

[1354 rows x 5 columns]

可以看到,一张 DataFrame 由三部分组成:

  • 列名:最上面那一行,date 、item 、category 、cost 、payment 。它们来自 csv 文件的第一行。
  • 行号:最左边那一列,从 0 开始数,pandas 管它叫索引(index)。要注意,它不是文件里的数据,而是 pandas 自己加上去的。
  • 数据:除了列名和行号,中间的部分就是数据。

表中间那一行 ... 又是什么意思呢?因为这张表太长了,pandas 只显示开头 5 行和结尾 5 行,中间的部分就用 ... 省略掉了。最后一行还告诉你,这张表一共有 1354 行、5 列。

其实很多时候,咱们并不需要看整张表,只想看看开头或者结尾。这时候就用 head() 和 tail() :括号里写要看几行,不写的话,默认就是 5 行。

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

ledger = pd.read_csv('ledger_2026.csv')
print(ledger.head(3))
print(ledger.tail(2))

输出的结果:

文本
         date      item category    cost payment
0  2026-01-01      房租     居住  2500.0  银行卡
1  2026-01-01  水电燃气     居住   156.0  支付宝
2  2026-01-01      早饭     餐饮    13.0  支付宝
            date  item category  cost payment
1352  2026-12-31  地铁     交通   4.0    微信
1353  2026-12-31  零食     餐饮   9.5    微信

head(3) 打印出开头 3 行,tail(2) 打印出结尾 2 行。所以拿到一份数据,第一件事就是 head() 一下,看看每一列长什么样。

最后还有一个小地方。细心的童鞋可能注意到了,表里的中文能对得这么整齐,其实是因为网页里已经替你打开了 pandas 的一个显示选项。要是在自己电脑上运行,就在 import pandas as pd 下面加上这一行,效果是一样的:

Python· 结合前文阅读
pd.set_option('display.unicode.east_asian_width', True)

不加也没关系,数据还是一样的,只是汉字比较多的列会有点歪。

教学 03 / 08

二、看清表的样子

上一节是用眼睛看表。可是一千多行的表,光靠眼睛是看不全的。那么,这张表到底有多少行、有哪些列、每一列又是什么类型?这些基本情况,几行代码就能问清楚:

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

ledger = pd.read_csv('ledger_2026.csv')
print(ledger.shape)
print(len(ledger))
print(list(ledger.columns))
print(ledger.dtypes)

输出的结果:

文本
(1354, 5)
1354
['date', 'item', 'category', 'cost', 'payment']
date         object
item         object
category     object
cost        float64
payment      object
dtype: object

这四个 print() ,各问了一件事:

  • shape 是一个元组,写的是(行数,列数)。
  • len(ledger) 得到的是行数。
  • columns 是所有的列名。用 list() 把它变成普通的列表,打印出来好看一些。
  • dtypes 是每一列的数据类型。

这四样里面,数据类型最要紧。为什么呢?因为一列是什么类型,决定了它能拿来做什么。pandas 读文件的时候,会看看每一列的内容,自己判断它是什么类型。常见的有下面这几种:

类型意思记账本里的
float64小数cost 金额
int64整数这里没有
object一般就是文字日期、名称、类别、付款方式

对照着输出来看:cost 是 float64 ,说明 pandas 认出了它是数字,可以直接拿来加减。日期是 object ,说明 pandas 现在只把它当成文字。要把它当成日期来用,还得转换一下,这个第 5 章会讲。

一项一项地问,要写好几行。有没有办法把这些信息一次看全呢?有,用 info() :

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

ledger = pd.read_csv('ledger_2026.csv')
ledger.info()

输出的结果:

文本
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1354 entries, 0 to 1353
Data columns (total 5 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   date      1354 non-null   object 
 1   item      1354 non-null   object 
 2   category  1354 non-null   object 
 3   cost      1354 non-null   float64
 4   payment   1354 non-null   object 
dtypes: float64(1), object(4)
memory usage: 31.8+ KB

先说一个要留意的地方:info() 自己就会打印,不用再在外面套 print() 。

再来看输出里的 Non-Null Count ,它说的是每一列非空的值有几个。这里每一列都是 1354,和总行数一样,说明这份账本没有缺失的数据。要是哪一列只有一千出头,那就说明这一列有空着的地方。第 5 章清洗数据时,第一件事就是看这个数。

整张表的样子看清楚了。下一节,咱们把目光放到其中的一列上。在那之前,先做一道练习:用同样的办法,看看两点书店的书目有多大。

练习 1 / 3两点书店的书目有多大

books.csv 是两点书店的书目,爬虫课第 9 章就是把它抓下来的。读进这份文件,先打印它的行数和列数,再把列名变成列表打印出来。

运行后的输出应该是:

文本
(120, 10)
['id', 'title', 'author', 'category', 'price', 'rating', 'reviews', 'stock', 'pages', 'year']
💡 提示(3 条)
  1. 行数和列数在 shape 里,列名在 columns 里

  2. 列名用 list(books.columns) 变成列表

  3. 完整代码:

    Python
    import pandas as pd
    
    books = pd.read_csv('books.csv')
    print(books.shape)
    print(list(books.columns))
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
教学 04 / 08

三、一列就是一个 Series

整张表看清楚了,可真到了算数的时候,咱们往往只关心其中的一列。比如想知道一年一共花了多少钱,只用得着金额这一列。那怎么把一列单独取出来呢?很简单,在方括号里写上列名就行:

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

ledger = pd.read_csv('ledger_2026.csv')
cost = ledger['cost']
print(type(cost))
print(cost.head(3))

输出的结果:

文本
<class 'pandas.core.series.Series'>
0    2500.0
1     156.0
2      13.0
Name: cost, dtype: float64

第一行告诉我们,取出来的这一列,类型叫 Series。那 Series 是什么呢?可以把它看成一串值:每个值都带着自己的行号,最下面还写着列名和数据类型。反过来想,一张 DataFrame,其实就是好几个 Series 并排放在一起。

把一列取出来,有什么用呢?因为 Series 自带了很多统计方法,想算什么,直接调用就行:

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

ledger = pd.read_csv('ledger_2026.csv')
cost = ledger['cost']
print(cost.sum())
print(cost.max(), cost.min())
print(round(cost.mean(), 2))
print(cost.count())

输出的结果:

文本
69195.5
2500.0 3.0
51.1
1354

从上往下,依次是总和、最大和最小、平均数,还有一共有几个值。一个方法就得到一个数,不用再像上一章那样写 for 循环,一个一个地加。

数字列能算,那文字列呢?文字虽然没法加减,不过也能看出不少东西:

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

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

输出的结果:

文本
['居住' '餐饮' '交通' '娱乐' '购物' '学习' '医疗']
7

unique() 会列出这一列里出现过的所有不同的值,nunique() 则告诉你一共有几种。一共七个类别,一目了然。

上面取的都只是一列。要是想同时取好几列呢?那就在方括号里放一个列表。这回得到的就不是 Series 了,还是一张 DataFrame:

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

ledger = pd.read_csv('ledger_2026.csv')
print(ledger[['item', 'cost']].head(3))

输出的结果:

文本
       item    cost
0      房租  2500.0
1  水电燃气   156.0
2      早饭    13.0

这里要注意,写的是两层方括号:外面一层表示「取列」,里面一层是列名组成的列表。

取出一列,一个方法算一个数,已经很方便了。先做一道练习,拿书店的价格这一列试一试。做完了,下一节再看一个更省事的办法:一次就把好几个数都算出来。

练习 2 / 3最贵、最便宜和平均价格

读进 books.csv ,取出价格这一列(price),分三行打印:最贵的价格、最便宜的价格、平均价格。平均价格用 round() 保留两位小数。

运行后的输出应该是:

文本
117.2
20.0
58.18
💡 提示(3 条)
  1. 先 price = books['price'] ,再用 max()、min()、mean()

  2. 平均值写成 round(price.mean(), 2)

  3. 完整代码:

    Python
    import pandas as pd
    
    books = pd.read_csv('books.csv')
    price = books['price']
    print(price.max())
    print(price.min())
    print(round(price.mean(), 2))
在编辑器里写这题,跑判分、看参考答案去闯关页做这题 →
教学 05 / 08

四、describe() 一眼看全局

上一节算总和、平均、最大、最小,是一个方法算一个数。要是想把这些数放在一起看,一个一个地调用就有点麻烦了。这时候可以用 describe() ,它一次就能算出好几个统计量:

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

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['cost'].describe())

输出的结果:

文本
count    1354.000000
mean       51.104505
std       234.946263
min         3.000000
25%         9.625000
50%        21.000000
75%        32.000000
max      2500.000000
Name: cost, dtype: float64

输出里,每一行都是一个统计量。它们分别是什么意思呢?对照这份账本,一行一行地看:

统计量意思这份账本里
count有几个值1354 笔
mean平均数平均每笔约 51 元
std标准差,数值有多分散235,很分散
min / max最小、最大最少 3 元,最多 2500 元
25% / 50% / 75%四分位数:排好序以后,排在四分之一、正中间、四分之三处的那个值一半的花销不超过 21 元

看完这张表,有没有发现什么奇怪的地方?平均每笔 51 元,可排在正中间的那一笔只有 21 元;而且四分之三的花销,都不超过 32 元。也就是说,平均数比大多数花销都要大得多。

这是为什么呢?因为账本里有几笔 2500 元的房租,把平均数拉高了。这就是第 1 章说过的:平均数很容易被少数几个特别大的值带偏。这时候,排在正中间的那个数(50% ,也叫中位数)更能代表「一笔普通的花销」。这一点,第 4 章还会细讲。

所以说,describe() 的用处,并不是让你把这些数抄下来,而是帮你发现问题:最大值是不是大得离谱?最小值是不是出现了负数?平均数和中位数是不是差得很远?哪里看着不对劲,就去原始数据里看个究竟。

教学 06 / 08

五、常见坑

看表的这几样工具,用起来都不难。不过刚上手的时候,还是有几个坑很容易踩到,有的还不会报错,踩了都不知道。咱们一个一个来看。

1、列名写错了

先看一段代码,各位童鞋能看出哪里写错了吗?

Python· 预期报错示例
import pandas as pd

ledger = pd.read_csv('ledger_2026.csv')
print(ledger['Cost'].sum())

报错信息的最后一行是:

文本
KeyError: 'Cost'

为什么会找不到呢?因为列名是区分大小写的:明明是 cost ,这里写成了 Cost ,pandas 就找不到这一列了。所以拿不准列名的时候,先 print(list(ledger.columns)) 看一眼。

2、数字列被认成了文字

列名写错,好歹会报错。下面这个坑,代码不会报错,所以更不容易发现。

ledger_2026_raw.csv 是一份还没整理过的账单,第 5 章会用它来练习清洗。先来看看它的数据类型:

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

raw = pd.read_csv('ledger_2026_raw.csv')
print(raw.dtypes['cost'])
print(raw['cost'].head(4).tolist())

输出的结果:

文本
object
['2500', '229.5', '8', '22.5元']

金额这一列的类型变成了 object ,取出来的也都是字符串。这是怎么回事呢?因为有的金额后面写了「元」,比如第 4 个值 '22.5元' 。pandas 一看这一列里有不是数字的东西,就把整列都当成了文字。这时候要是对它求和,得到的不是总金额,而是一长串拼接起来的文字。

所以读进数据以后,先看看 dtypes :本该是数字的列,是不是真的是数字?

3、只看了打印出来的那几行

print(ledger) 只会显示开头和结尾各 5 行,中间一千多行都被省略掉了。可问题往往就藏在没显示出来的地方。所以别光靠眼睛看,要用 info() 、describe() 、unique() 这些方法,让 pandas 替你把整列都检查一遍。

4、info() 外面又套了 print()

Python· 结合前文阅读
print(ledger.info())

这样写会怎么样呢?info() 自己就会打印,它的返回值是 None 。外面再套一个 print() ,就会把这个 None 也打印出来,所以表格下面会多出一行 None 。实战 4 讲的「只打印,没有返回」,说的就是这类函数。

练习 3 / 3有几本书还没有评分

有的书还没有人打分,rating 这一列就空着。读进 books.csv ,先打印 rating 列有几个值,再打印有几本书没有评分。

count() 只数非空的值,用总行数减去它,就是空着的个数。

运行后的输出应该是:

文本
109
11
💡 提示(3 条)
  1. 非空值的个数:books['rating'].count()

  2. 总行数是 len(books),两个数相减

  3. 完整代码:

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

六、自己查一次文档

第四节用 describe() 看的是金额这一列。不过,describe() 默认只统计数字列,像日期、名称、类别这些文字列,它就不管了。那么,文字列能不能也让它看一看呢?这种问题,最靠得住的办法就是去查文档:DataFrame.describe 。

打开说明,参数里有一个 include ,就是管「统计哪些列」的。说明里提到了它的三种用法:

  • 不写的话,默认只统计数字列。
  • 设成 'all' ,所有的列都统计。
  • 也可以传一个类型的列表,只统计这些类型的列。

文档下面的例子里,正好有一条是 df.describe(include=[object]) ,专门用来看 object 类型的列。

查到了,就动手试一试:

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

ledger = pd.read_csv('ledger_2026.csv')
print(ledger.describe(include=[object]))

输出的结果:

文本
              date  item category payment
count         1354  1354     1354    1354
unique         365    22        7       4
top     2026-12-18  午饭     餐饮  支付宝
freq             8   326      984     582

可以看到,文字列的统计量和数字列不一样。除了 count 还是有几个值,其余几项都换了:

  • unique :有几种不同的值。日期有 365 种,说明一年里每天都记了账;名称有 22 种;类别有 7 种;付款方式有 4 种。
  • top :出现次数最多的那个值。
  • freq :这个值一共出现了几次。

这样一来,一眼就能看出不少东西:记得最多的是「午饭」,有 326 笔;类别里「餐饮」最多,有 984 笔,占了一大半;最常用的付款方式是「支付宝」。日期那一列,2026-12-18 记了 8 笔,是一年里记账最多的一天。

不过,top 有一点要留意,文档的说明里也写了:要是有好几个值出现的次数一样多,top 会从中随便挑一个给你。这份账本里,记了 8 笔的只有这一天,所以没问题;可要是有两天都记了 8 笔,这里也只会显示其中一天。所以读统计结果的时候,要多问一句:「它是怎么算出来的?」

教学 08 / 08

七、小结与回看

这一章没有急着算,而是先把记账本这张表,从整张表到每一列,都看了个清楚。回头梳理一下,学到了这几件事:

  • DataFrame 就是一张表,由列名、索引(行号)和数据三部分组成。想看开头和结尾,就用 head() 和 tail() 。
  • shape 是行数和列数,columns 是列名,dtypes 是每一列的数据类型:float64 、int64 是数字,object 一般是文字。
  • info() 能把这些一次看全:每一列的类型,还有非空值的个数。非空值比总行数少,就说明有缺失。
  • ledger['列名'] 能取出一列,它是一个 Series,自带 sum() 、mean() 、max() 、count() 、unique() 、nunique() 这些方法。要取好几列,就用两层方括号。
  • describe() 能一眼看全局,用来发现问题,比如平均数和中位数差得很远、最大值大得离谱、出现了负数……
  • 常见的坑有这几个:列名写错,会报 KeyError ;数字列混进了文字,会变成 object ;只看打印出来的那几行,会漏掉中间的问题。
  • describe(include=[object]) 能统计文字列:有几种值,最多的是哪个,它出现了几次。

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

  • 元组和列表:第 3 章
  • 「只打印、不返回」:实战 4 的「六、常见坑」

这一章看的,都是整张表或者一整列。可分析的时候,咱们常常只关心其中的一部分。所以下一章,就来学怎么从这张表里挑出想要的部分:只要某几列、只要某几行,或者只要满足条件的那些行。

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

去闯关模式练习 →

学习目录